これは、Flutter Architectureに関する私のシリーズの第2部です。
- はじめに
- Dart Streams Basics(この投稿)
- RxDart:マジックストリーム変換
- RxVMSの基本:RxCommandとGetIt
- RxVMS:サービスとマネージャー
- RxVMS:自己完結型のウィジェット
- RxVMSを使用したユーザー認証
ストリームはRxVMSの主要な構成要素であり 、このライブラリを操作するにはそれらの理解が絶対に必要であるため、この投稿ではストリームについて詳しく説明します。
この投稿にRxを含めると長すぎることが判明したため、2つの部分に分割しました。
流して
フロー、特にRxは複雑すぎて理解できず、結果として使用できないというコメントをたくさん読みました。
私は自分をRxの第一人者とは考えていないことを知ってほしい。 彼の力をすべて活用するのは簡単ではありません。私は勉強を続けていることを認めます。 ただし、最初から1つの間違いを修正しましょう。 スレッドとこのテクノロジーを使用して多くのメリットを得るには、Rxウィザードである必要はありません 。 フローを最もアクセスしやすい方法で説明するようにあらゆる努力をします。
ストリームとは何ですか?
私の意見では、スレッドに最も近いのはコンベアベルトです。 あなたはそれの一端に何かを置くことができ、この「何か」は自動的にもう一方に転送されます。 物理パイプラインとは異なり、スレッドはデータオブジェクトを操作し、最初から自動的に転送しますが、どこで? 実際のパイプラインのように、もう一方の端でデータをキャッチするものがない場合、それらは単に「落ち」て消えます(もちろん、これはDart Streamsにはまったく当てはまりませんが、そのようにストリームを処理するのが最善です) 。
データの損失を防ぐために、ストリーム出力に「トラップ」を設定できます。 これにより、データオブジェクトがストリームの最後に到達するたびに、データをキャプチャし、必要な操作を実行できます。
覚えておいてください:
- トラップが設定されていない場合、データは単純に永久に消え、再び取得する方法はありません(再び、Dart Streamsでは正確ではありませんが、それを装う方が良いでしょう)
- ストリームにデータを送信した後、プログラムを一時停止して終了するまで待つ必要はありません。これらはすべてバックグラウンドで行われます。
- トラップはいつでもデータを受信できます。送信直後は必要ありません(ただし、ストリームは実際には非常に高速です)。 コンベアベルトの移動速度や長さがわからないことを想像してください。 つまり、ストリームに何かを配置することは、もう一方の端の要素に対する反応とは完全に分離されます。 トラップは機能し、アイテムが到着するとキャッチします。 (これは、Flutterがウィジェットを更新するリアクティブな方法とうまく適合することを既にご存じかもしれません)
- 作業を開始し、最初の項目が表示されるかなり前にトラップを設定できます
- フローはFIFOの原理に基づいて機能します。 データは常に、ストリームに配置された順番になります。
Rxとは何ですか?
Reactive Extensionsの略であるRxは、ステロイドストリームです。 これは、Microsoftチームが.Netフレームワーク用に考案したStreamsに非常によく似た概念です。 .Netには既にファイルI / Oに使用されるStreamタイプがあるため、RxストリームにObservablesという名前を付け、通過するデータを操作する多くの関数を作成しました。 Dartには、言語仕様に組み込まれたStreamsがあります。これは、すべてではありませんが、ほとんどの機能を既に提供しています。 これが、RxDartパッケージが開発された理由です。 Dart Streamsに基づいていますが、機能を拡張しています。 このシリーズの次のパートでは、RxとRxDartについて説明します。
いくつかの用語
Dart StreamsとRxは怖いように見えるかもしれない用語を使用しているので、ここに翻訳があります。 最初にDartという用語、次にRxという用語が来ます。
- ストリーム/観測可能 。 これは、前述の「パイプライン」です。 ストリームはObservableに変換でき、Streamが予想される場所ならどこでもObservableを割り当てることができます。 だから、説明の過程でこれらの用語を混ぜても混乱しないでください
- listen / subscribe- リスナートラップを設定します
- StreamController / Subject コンベアベルトの「左側」は、データをストリームに配置する場所です。 プロパティと特性はわずかに異なりますが、同じ目的を果たします。
- アイテム/データの発行 。 「パイプライン」の出口にデータが表示される瞬間
ストリーム作成
トピックの学習を続ける場合は、 このプロジェクトのクローンを作成してください。 Dart / Flutterテストシステムを使用します。
ストリームを作成するには、 StreamControllerを作成します
var controller = new StreamController<String>(); controller.add("Item1"); //
StreamControllerの作成時に渡されるテンプレートタイプ(この場合はString)は、ストリームに送信できるオブジェクトのタイプを決定します。 どのタイプでもかまいません! 必要に応じてStreamController<List<MyObject>>()
を作成できます。ストリームは単一のオブジェクトではなくシート全体を転送します。
トラップ設定
指定されたテストを実行した場合、ストリームの出力で行がキャッチされなかったため、何も表示できませんでした。 次に、トラップを設定します。
var controller = new StreamController<String>(); controller.stream.listen((item) => print(item)); // controller.add("Item1"); controller.add("Item2"); controller.add("Item3");
これで、トラップは.listen()
メソッドを使用して設定されます。 レコードはcontroller.stream.listen
ように見えますが、60年代のアルバムのように後方にスクロールすると、「このコントローラーのストリームを聴く」という真の意味が表示されます。
受信データを何らかの方法で操作するには、特定の関数を.listen()
メソッドに渡す必要があります。 この関数は、StreamControllerの作成時に指定されたタイプのパラメーター(この場合はString)を受け入れる必要があります。
上記のコードを実行すると、表示されます
Item1 Item2 Item3
私の意見では、Streamsの新規参入者にとって最大の問題は、最初の要素がストリームに配置されるずっと前に、放出された要素の反応を決定でき、この反応の呼び出しをトリガーできることです。
リスニングを終了
上記のコードでは、小さいながらも重要な部分が欠落しています。 listen()
はStreamSubscription
ストリームサブスクリプションオブジェクトを返します。 彼の.cancel()
メソッドを呼び出すと、サブスクリプションが終了し、リソースが解放され、リスニング機能が不要になった後の呼び出しが防止されます。
var controller = new StreamController<String>(); StreamSubscription subscription = controller.stream.listen((item) => print(item)); // This is the Trap controller.add("Item1"); controller.add("Item2"); controller.add("Item3"); // , // , Stream await Future.delayed(Duration(milliseconds: 500)); subscription.cancel;
リスナーの詳細
listen()
の関数は、ラムダまたは単純な関数のいずれかです。
void myPrint(String message) { print(message); } StreamSubscription subscription = controller.stream.listen((item) => print(item)); // - StreamSubscription subscription2 = controller.stream.listen(myPrint); // StreamSubscription subscription3 = controller.stream.listen((item) { print(item); print(item.toUpperCase); }); // -
重要な注意:ほとんどのDartストリームでは、1回限りのサブスクリプションしか許可されていません。つまり、サブスクリプションの完了後に再サブスクライブすることはできません。これは例外をスローします。 これは、他のRx実装との違いです。
listen()
の完全な署名は次のようになります。
/* excerpt from the API doc * The [onError] callback must be of type `void onError(error)` or * `void onError(error, StackTrace stackTrace)`. If [onError] accepts * two arguments it is called with the error object and the stack trace * (which could be `null` if the stream itself received an error without * stack trace). * Otherwise it is called with just the error object. * If [onError] is omitted, any errors on the stream are considered unhandled, * and will be passed to the current [Zone]'s error handler. * By default unhandled async errors are treated * as if they were uncaught top-level errors. * * If this stream closes and sends a done event, the [onDone] handler is * called. If [onDone] is `null`, nothing happens. * * If [cancelOnError] is true, the subscription is automatically canceled * when the first error event is delivered. The default is `false`. */ StreamSubscription<T> listen(void onData(T event), {Function onError, void onDone(), bool cancelOnError});
これは、送信されたデータに対して1つのハンドラーを渡すだけでなく、複数のことができることを意味します。 また、エラーのハンドラーと、コントローラー側でストリームを閉じるハンドラー( onDone
)を持つこともできます。 Stream内からonError()
れた例外は、 指定した場合にonError()
をonError()
ます。 それ以外の場合、例外は単に飲み込まれ、何かがうまくいかなかったことは決してわかりません。
フラッタースレッドの例
次の章の理解を容易にするために、別のリポジトリブランチを作成しました。
彼女をクローンしてください
最初の例として、新しいFlutterプロジェクトを作成するときに取得する有名なカウンターアプリケーションを取り上げ、少し再編成しました。 アプリケーションの状態を保存するモデルクラスを追加しました。これは基本的にカウンター値です。
class Model { int _counter = 0; StreamController _streamController = new StreamController<int>(); Stream<int> get counterUpdates => _streamController.stream; void incrementCounter() { _counter++; _streamController.add(_counter); } }
ここでは、非常に典型的なテンプレートを見ることができます。StreamController全体を公開する代わりに、単にStreamプロパティを公開します。
モデルをUIで使用できるようにするために、InheritedWidgetまたはServiceLocatorを入力したくないため、Appオブジェクトの静的フィールドにしました。 簡単な例では、これでうまくいきますが、このアプリケーションでは行いません!
main.dart
追加しmain.dart
。
class _MyHomePageState extends State<MyHomePage> { int _counter = 0; StreamSubscription streamSubscription; @override void initState() { streamSubscription = MyApp.model.counterUpdates.listen((newVal) => setState(() { _counter = newVal; })); super.initState(); } // State , , // @override void dispose() { streamSubscription?.cancel(); super.dispose(); }
initState()
リスナーを設定initState()
適した場所です。ダーツの良き市民として、私たちは常にdispose()
でサブスクリプションをリリースします。
ウィジェットツリーでは、FABボタン(フローティングアクションのあるボタン)のonPressedハンドラーを調整するだけです。
floatingActionButton: new FloatingActionButton( onPressed: MyApp.model.incrementCounter, tooltip: 'Increment', child: new Icon(Icons.add), ),
このようにして、Streamを使用して、ViewとModelを明確に分離しました。
StreamBuilderを適用する
必要に応じてinitState()
およびsetState()
を使用する代わりに、Flutterには便利なStreamBuilder
ウィジェットが付属しています。 ご想像のとおり、Stream関数と、Streamが新しい値を返すたびに呼び出されるコンストラクターメソッドを取ります。 そして今、明示的な初期化とリリースは必要ありません:
body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text( 'You have pushed the button this many times:', ), StreamBuilder<int>( initialData: 0, stream: MyApp.model.counterUpdates, builder: (context, snappShot) { String valueAsString = 'NoData'; if (snappShot != null && snappShot.hasData) { valueAsString = snappShot.data.toString(); } return Text( valueAsString, style: Theme.of(context).textTheme.display1, ); }), ], ), ),
ほぼ完了です、約束します。 知っておくべき3つのことを次に示します。
- StreamBuilderを最初のソリューションよりも使用することの大きな利点は、
listen()
でsetState()
を呼び出すと常にページ全体が再配置されるのに対して、StreamBuilderはそのbuilder
のみを呼び出すことです。 -
snapShot
変数には、Streamから受信した最新のデータが含まれています。 使用する前に、有効なデータが含まれていることを常に確認してください。 初期化の原則に基づいて、StreamBuilderは最初のフレームで値を取得できません。 これを回避するには、
initialData
の値をinitialData
。これは、最初のビルド、つまり画面の最初のフレームに使用されます。initialData
ない場合、ビルダーは無効なデータで初めて呼び出されます。initialData
を使用する代わりに、snapShot
無効な場合にプレースホルダーウィジェットを返すこともsnapShot
ます。これは、有効なデータを取得するまで表示されます。次に例を示します。
// , StreamBuilder<int>( stream: MyApp.model.databaseUpdates, builder: (context, snappShot) { if (snappShot != null && snappShot.hasData) { return Text( snappShot.data.toString(), style: Theme.of(context).textTheme.display1, ); } // , Spinner return CircularProgressIndicator (); })
次の投稿では、スレッド内のデータを変換し、その場でそれを行う方法を見ていきます。 証拠と重要なフィードバックを読んでくれたScott Stollに感謝します。