Flutterやってみたよ part6 （状態管理導入 BLoC編 ）

# はじめに

[前回のChangeNotifierProvider導入](https://github.com/190yamayama/flutter_bloc_sample)のアプリをBLoCで書き換えます。
完全に独学なので間違いありましたらご指定いただけたら幸いです🙇‍♂️

# BLoCについて
Providerが進化する前までGoogle推奨状態管理がBLoCでした。
BLoCの３原則は

- 入出力インターフェースはStream/Sinkであること
- 依存は必ず注入可能でプラットフォームに依存しないこと
- BLoC内にプラットフォームによる分岐処理を書くことは禁止

です。
RxSwiftやRxKotlinやってた人ならStream/Sinkはすんなり理解できると思いますが、そうじゃない人はなかなかとっつきにくいかもですね。
その辺が敷居高い原因となっているのかもしれません。

例によって良記事がたくさんあるので詳細は割愛します🙇‍♂️

# ソース

では早速SingInScreenでソースをみていきます。

説明はいいから早くソースが必要って方は[こちら](https://github.com/190yamayama/flutter_bloc_sample)に全ソースがあります。

画面のソース

```dart

class SignInScreen extends StatefulWidget {
  SignInScreen({Key key}) : super(key: key);

  static final formKey = new GlobalKey<FormState>();

  @override
  _SignInScreenState createState() => _SignInScreenState();
}

class _SignInScreenState extends State<SignInScreen> {

  @override
  Widget build(BuildContext context) {
    // Providerで全体を囲みます。
    // そうすることで下層のwidget内のどこからでもアクセスできるようになります。
    return Provider<SignInScreenBloc>(
      // createでBlocの実態を生成
      create: (context) => SignInScreenBloc(context),
      // disposeが必須なのでBloc内にdisposeを用意
      dispose: (context, bloc) => bloc.dispose(),
      child: SignInScreenPage(),
    );
  }

}

// 状態管理と分離しているのでStatelessWidgetでOK
class SignInScreenPage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sign In Screen"),
      ),
      body: new Center(
        child: new SingleChildScrollView(
          padding: const EdgeInsets.symmetric(horizontal: 16.0),
          child: new Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: <Widget> [
              // 省略・・画面のコンテンツ・・
            ],
          ),
        ),
      ),
    );
  }

  Widget emailText(BuildContext context) {
    // Providerでblocへの参照を取得
    final bloc = Provider.of<SignInScreenBloc>(context);
    return padded(child: new TextFormField(
      key: new Key("email"),
      decoration: new InputDecoration(labelText: "Email"),
      autocorrect: false,
      validator: (val) => val.isEmpty ? "emailを入力してください" : null,
      // 値の入力はstream/shinkなのでadd
      onSaved: (val) => bloc.email.add(val),
    ));
  }

  Widget hintText(BuildContext context) {
    // Providerでblocへの参照を取得
    final bloc = Provider.of<SignInScreenBloc>(context);
    return new Container(
      padding: const EdgeInsets.all(32.0),
      // 値の出力はStreamBuilderを使用する
      child: StreamBuilder(
        // 監視するStreamを指定
        stream: bloc.errorMessage,
        builder: (context, snapshot) {
          // snapshotにはstreamの最後の値が入ってるのでdataで取り出す
          return Text(
              (snapshot.data ?? ""),
              key: new Key("hint"),
              style: new TextStyle(fontSize: 18.0, color: Colors.grey),
              textAlign: TextAlign.center
          );
        },
      )
    );
  }

}
```

Blocのソース

```dart

class SignInScreenBloc {

  // 入出力のデータはStream/shink（ここではRxDartのBehaviorSubjectを使用）
  final _authentication = BehaviorSubject<Authentication>();
  final email = BehaviorSubject<String>();
  final password = BehaviorSubject<String>();
  final errorMessage = BehaviorSubject<String>();

  AuthenticationRepository _repository = AuthenticationRepository();

  // 依存性は注入可能である必要があるので、コンストラクタでインジェクションする
  SignInScreenBloc(BuildContext context, [AuthenticationRepository repository]) {
    _repository = repository ?? AuthenticationRepository();
    // 状態をlistenしておく
    _authentication.listen((value) {
      // 状態の入力はStreamなのでadd
      errorMessage.add(value.errorMessage ?? "");
      if (value.authStatus == AuthStatus.signedIn) {
        // 何かしらの処理
      }
    });
  }

  void signIn(BuildContext context) {
    final ProgressDialog progress = new ProgressDialog(context);
    progress.show();
    _repository.signIn(email.value, password.value)
        .then((value) {
          progress.hide();
          // 状態の入力はStreamなのでadd
          _authentication.add(value);
        });
  }

  // Streamの開放が必要なので開放メソッドを準備する
  void dispose() {
    _authentication.close();
    email.close();
    password.close();
    errorMessage.close();
  }


```

このように入力はStreamにAdd、出力はStreamBuilderで出力することで画面描写できます。
制約がありますがそこまで難しい訳でもないと思います。

# 終わりに
Rxやってる人にはしっくりきますね。input/output/disposeに気を付ければBLoCもありなんじゃないかと思います。コード量の少なさや理解のしやすさではChangeNotifierProviderに軍配が上がりそうです。
完成したサンプルコードは[こちら](https://github.com/190yamayama/flutter_bloc_sample)です。

一通り状態管理の学習も進みましたし、次はテストコードの書き方について勉強したいと思います。

# 参考
大変参考になりました！ありがとうございました🙇‍♂️
- [長めだけどたぶんわかりやすいBLoCパターンの解説](https://qiita.com/kabochapo/items/8738223894fb74f952d3)
- [FlutterのBLoC(Business Logic Component)のライフサイクルを正確に管理して提供するbloc_providerパッケージの解説](https://medium.com/flutter-jp/bloc-provider-70e869b11b2f)