12
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

flutter_blocを使ったBLoC入門

Last updated at Posted at 2020-09-08

Flutter歴1週間の私が flutter_bloc - pub.devなんとなく 使えるようになるまでのまとめです。

いろいろ試して遊んでいたリポジトリも貼っておきます → flutter-bloc-test
以下ではソースコードを抜粋して説明していきますので、全コードを読みたいときはこのリポジトリを参照してください。

BLoCパターン

BLoC = Business Logic Component

StateをViewから切り離して管理することで、

  • キレイ (疎) に実装できる!
  • Widget間でのデータのやり取りもできる!

StateはRxっぽく (違うところもあるけど) 更新できる!

って感じ。違ったらごめんなさい。


  • もっと厳密で詳しい説明が必要な方
  • BLoCがどうやってデータを保持・更新しているか知りたい方

などには、丁寧でわかりやすい記事を書いてくださっている方がいらっしゃるので、そちらへどうぞ。
(私も練習中に参考にさせていただきました。ありがとうございました。)

Bloc型

Rxと聞いてビビっている方に朗報です。
flutter_bloc における Bloc型 は「Rxらしい」部分が
とてもいい感じにラップされているので安心してご利用いただけます。

Cubit と Bloc

flutter_bloc には Cubitクラス と Blocクラス というお膳立てされたBLoCがあるのでStreamを直接触る必要はありません。
どちらも同じような働きをするのですが、理解しやすいCubitから見ていきます。

Cubit

CubitクラスはStateの型を受け取ります。
例のカウンターアプリを例にとると、

lib/blocs/counter.dart
import 'package:flutter_bloc/flutter_bloc.dart';

class CounterCubit extends Cubit<int> {  // カウンターなのでStateはint型
  CounterCubit() : super(0);  // 初期値をsuperにわたす

  void increment() {
    emit(state + 1);  // emitでstateの更新と変更通知
  }
}

あとはボタンでincrementを呼べば 1 ずつ足されていきます。

Bloc

対してBlocクラスはCubitクラスにイベント種別を渡せるようにしたものです。
上の例と同様のBLoCを実装すると、

lib/blocs.counter.dart
import 'package:flutter_bloc/flutter_bloc.dart';

enum CounterEvent { increment }  // incrementイベントを定義

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0);  // ここはCubitと同じ

  Stream<int> mapEventToState(CounterEvent event) async* {  // イベント種別が渡ってくる
    switch (event) {
      case CounterEvent.increment:  // incrementイベントを受け取ったときの操作を定義
        yield state + 1;
        break;
      default:
        addError(Exception('unsupported event'));
    }
  }
}

こっちはBlocインスタンスに生えているaddメソッドにイベント種別を渡すとStateの更新ができます。
詳しくは次項かGitHubで…

Blocの利点

Cubitと同じ動作をBlocにさせる意味は無いと言って良いでしょう。
上でも述べたように、Blocを使った真骨頂は「イベント種別を渡せる」ことです。
つまり、同一のStateに2つ以上の操作を定義したいときはCubitではなくBlocを使おう、ということです。

元々のFlutterデフォアプリにはカウンターのdecrement操作はありませんが、Blocを使えば簡単に追加できます。

lib/blocs/counter.dart
import 'package:flutter_bloc/flutter_bloc.dart';

enum CounterEvent { increment, decrement }  // decrementイベントを追加

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0);

  Stream<int> mapEventToState(CounterEvent event) async* {
    switch (event) {
      case CounterEvent.increment:
        yield state + 1;
        break;
      case CounterEvent.decrement:  // decrementイベントを受け取ったときの操作を定義
        if (state <= 0) {  // カウンターが負の値を取らないように管理
          break;
        }
        yield state - 1;
        break;
      default:
        addError(Exception('unsupported event'));
    }
  }
}

また、CubitはBlocを継承しているので、定義するときは少し書き方が違いますが、使う先ではかなり互換性があります。

Provider

BLoC (State) を Widget (View) と疎な感じで結びつけて使えるようにするにはProviderを使います。
あの有名なproviderパッケージを使うのではなく、
flutter_blocの中にBlocProviderというものが提供されています。

flutter_blocさえimportしておけばなんでもできますね!

lib/main.dart
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: BlocProvider(
        create: (_) => CounterBloc(),  // 予め定義しておいたCounterBlocをインスタンス化する
        child: Home(title: 'Flutter Demo Home Page'),  // childにBLoCが渡る
      ),
    );
  }
}

Cubitなら

lib/widgets/home.dart
floatingActionButton: FloatingActionButton(
  tooltip: 'Increment',
  child: Icon(Icons.add),
  onPressed: () => context.read<CounterCubit>().increment(),
)

incrementを呼べます。

Blocなら

lib/widgets/home.dart
floatingActionButton: FloatingActionButton(
  tooltip: 'Increment',
  child: const Icon(Icons.add),
  onPressed: () => context.read<CounterBloc>().add(CounterEvent.increment),
),

addにincrementイベントを渡せます。

BLoCからStateを取得したいときは、表示するWidget (今回はText) をBlocBuilderで囲むだけ!

lib/widgets/home.dart
BlocBuilder<CounterBloc, int>(  // BlocBuilder<BLoC (Cubit/Bloc 共通), Stateの型>
  builder: (_, count) {  // builderの引数は BuildContext と State
    return Text(
      '$count',
      style: Theme.of(context).textTheme.headline4,
    );
  },
),

おわり

Flutterデフォアプリの flutter_bloc版 再実装はこれがすべてだと思います。
あとは、Stateの型を変えたり表示部分のWidgetを変えたりするといろいろ応用できるはずです。

実際に最初に紹介したテスト用のリポジトリでは、ボタンを押すたびにリストに要素が追加されていくページも作ってみましたので、困ったら(?)参考にしてください。

追記

2021/7/31
flutter_bloc v7.0.0 からBuildContextの bloc メソッドが削除され、readwatch を使うことになりました。
これに伴って本記事のコードも書き換えています。
Changelog

12
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?