【Dart】Streamで非同期的にデータを受け渡す

2020/10/09に公開

はじめに

Flutterで離れたスコープにあるウィジェットに対して値やイベントを渡したい場合、どのようにすれば良いのか気になったので調べてみました。

Streamとは

簡単に言うと、ストリームにデータを渡したら、ストリームの受け手がデータを検知して何らかの処理を開始する。というものでしょうか。今回はあまり深堀りせず使い方を説明していきたいと思います。
StreamController class - dart:async library - Dart API

実行環境

Dart programming language | Dart
Flutter on CodePen

簡単なサンプル

では少しコードを書いてみましょう。

import 'dart:async';

void main() {
  // Streamを制御するコントローラを定義
  var controller = StreamController<String>();
  
  // ストリームの受け手(リスナー)
  controller.stream.listen((data){
    print('通知:' + data);
  });

  // ストリームにデータを流す
  controller.sink.add('お知らせ1');
  controller.sink.add('お知らせ2');
}
/* output
通知:おしらせ1
通知:おしらせ2
*/

シンプルに書いちゃいましたけど、ストリームに渡したデータをリスナー側が検知して処理をしているということがわかるかと思います。

Flutterでの実際の使用場面

ではFlutterで実際の使い方を想定したコードを書いていきましょう。
イメージとしては、兄弟関係にある2つのウィジェットの片方のStateをもう片方から変更する、みたいな使い方かなと思ってます。
以下、今回作成したコードの全体像となります。

import 'package:flutter/material.dart';
import 'dart:async';

void main() {
  runApp(
    MaterialApp(
      home: MyWidget(),
    ),
  );
}

class MyWidget extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: <Widget>[
          Keys(), // 送り手
          KeyDisplay(), // 受け手
        ],
      ),
    );
  }
}

// ストリームを送るウィジェット
class Keys extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Center(
      child: Row(
        children: <Widget>[
          Key("A"),
          Key("B"),
          Key("C"),
        ],
      ),
    );
  }
}

class Key extends StatelessWidget {
  final String _key;
  
  // コンストラクタ
  Key(this._key);

  
  Widget build(BuildContext context) {
    return Expanded(
      flex: 3,
      child: FlatButton(
        color: Colors.lightBlue,
        child: Text(_key),
        onPressed: () {
          // ストリームにデータを流す
          _KeyDisplayState.controller.sink.add(_key);
        },
      ),
    );
  }
}

// ストリームを受け取るステートフルウィジェット
class KeyDisplay extends StatefulWidget {
  
  _KeyDisplayState createState() => new _KeyDisplayState();
}

// Stateクラス
class _KeyDisplayState extends State<KeyDisplay> {
  // コントローラーを定義
  static final controller = StreamController<String>();

  var _title = "";
  // 初期化メソッドをオーバーライドしてリスナーをセット
  
  void initState() {
    controller.stream.listen((key) {
      setState(() {
        _title = key;
      });
    });
  }

  
  Widget build(BuildContext context) {
    return Center(
      child: Text(
        _title,
        style: Theme.of(context).textTheme.display1,
      ),
    );
  }
}

完成した画面がこちらになります。
ボタンを押下すると、そのボタンのテキストと同じものが下に表示されます。

コードをパーツごとに見ていきましょう。
まずはステートにリスナーを定義します。ここではstaticで定義してコントローラーを参照させます。

// ストリームを受け取るステートフルウィジェット
class KeyDisplay extends StatefulWidget {
  
  _KeyDisplayState createState() => new _KeyDisplayState();
}

// Stateクラス
class _KeyDisplayState extends State<KeyDisplay> {
  // コントローラーを定義
  static final controller = StreamController<String>();

  var _title = "";
  // 初期化時にリスナーをセット
  
  void initState() {
    controller.stream.listen((key) {
      setState(() {
        _title = key;
      });
    });
  }

  
  Widget build(BuildContext context) {
    return Center(
      child: Text(
        _title,
        style: Theme.of(context).textTheme.display1,
      ),
    );
  }
}

あとはボタン側のウィジェットでKeyDisplayのコントローラーを参照して、Streamにデータを流しましょう!

// ストリームを送るウィジェット
class Keys extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Center(
      child: Row(
        children: <Widget>[
          Key("A"),
          Key("B"),
          Key("C"),
        ],
      ),
    );
  }
}

class Key extends StatelessWidget {
  // コンストラクタ
  final String _key;
  Key(this._key);

  
  Widget build(BuildContext context) {
    return Expanded(
      flex: 3,
      child: FlatButton(
        color: Colors.lightBlue,
        child: Text(_key),
        onPressed: () {
          // ストリームにデータを流す
          _KeyDisplayState.controller.sink.add(_key);
        },
      ),
    );
  }
}

これでウィジェット間でのデータのやり取りが簡単にできるってわけですね!

他にもデータの受け渡しをする方法は色々ある...らしい

RxDartとかProviderとか色々あるらしいんですけど、まぁまずは少しづつわかるところから理解していこうと思います。気が向けばこの辺りについても記事を書いてみようかなぁ。

参考文献

動かして理解する!dartのStreamとrxdart! - Qiita
【Flutter】電卓・計算機アプリの作成 2-開発記録

Discussion