Dart Streamsの基本

これは、Flutter Architectureに関する私のシリーズの第2部です。









ストリームはRxVMSの主要な構成要素であり 、このライブラリを操作するにはそれらの理解が絶対に必要であるため、この投稿ではストリームについて詳しく説明します。







この投稿にRxを含めると長すぎることが判明したため、2つの部分に分割しました。







流して



フロー、特にRxは複雑すぎて理解できず、結果として使用できないというコメントをたくさん読みました。







私は自分をRxの第一人者とは考えていないことを知ってほしい。 彼の力をすべて活用するのは簡単ではありません。私は勉強を続けていることを認めます。 ただし、最初から1つの間違いを修正しましょう。 スレッドとこのテクノロジーを使用して多くのメリットを得るには、Rxウィザードである必要はありません 。 フローを最もアクセスしやすい方法で説明するようにあらゆる努力をします。







ストリームとは何ですか?



私の意見では、スレッドに最も近いのはコンベアベルトです。 あなたはそれの一端に何かを置くことができ、この「何か」は自動的にもう一方に転送されます。 物理パイプラインとは異なり、スレッドはデータオブジェクトを操作し、最初から自動的に転送しますが、どこで? 実際のパイプラインのように、もう一方の端でデータをキャッチするものがない場合、それらは単に「落ち」て消えます(もちろん、これはDart Streamsにはまったく当てはまりませんが、そのようにストリームを処理するのが最善です) 。













データの損失を防ぐために、ストリーム出力に「トラップ」を設定できます。 これにより、データオブジェクトがストリームの最後に到達するたびに、データをキャプチャし、必要な操作を実行できます。













覚えておいてください:









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という用語が来ます。









ストリーム作成



トピックの学習を続ける場合は、 このプロジェクトのクローン作成してください。 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つのことを次に示します。









次の投稿では、スレッド内のデータを変換し、その場でそれを行う方法を見ていきます。 証拠と重要なフィードバックを読んでくれたScott Stollに感謝します。











All Articles