この記事を読むのに必要な時間は約 22 分です。
この記事では、お馴染みのデフォルトカウンターアプリを題材にbloc/cubit を使った状態管理を説明します。
全体のコードはGitHub に上げています。clone して、記事と照らし合わせながら使って下さい。
Cubit とは
ざっくり言うと…
Cubit とは、setState で行ってきた状態更新の処理をUI部分と切り分け、利用しやすくするためのクラスであり、状態管理の手法を指します。
Set Stateを使う状態管理と比べて、Cubitを使った状態管理のほうが画面描画するWidget と状態管理の処理を切り分けることが出来るため、可読性も上がるし、変更を修正するのも簡単です。複数の状態を持つ場合、特に威力を発揮します。
大規模な開発ではBloc(cubit) やriverpod といった状態管理手法が使われますね。
下記の図のようにUI は関数によってcubitを呼び出し、cubitは状態更新をstateとして渡します。
改めてメリットと使い所をまとめるとこんな感じ⬇︎
BLoC ( Business Logic Component)パターンのメリット
・可読性が高まる。
・メンテナンス、テストが容易になる。
・状態の把握が容易になる
・Widget 間のデータのやり取りが可能になる
BLoC パターンを使う場合
・API通信など非同期処理を含む場合
・Widget 間でデータを共有する場合
・可読性を上げたい場合
公式サイトにも説明があるため、参考にして下さい。
実装するアプリの概要
flutter create するとデフォルトで設定されているシンプルなカウンターアプリを例に、statefulwidget の代わりにcubit を使ったシンプルな状態管理を実装します。
出来上がるアプリはこんな感じ⬇︎
カウンターアプリにcubitを実装
①flutter_bloc パッケージの設定
まずcubit を使用するため、こちらのインストールページから最新版のパッケージ をコピーし、pubspec.yaml にflutter_bloc を追加します。
dependencies に追加したら保存し、pub getしましょう。右上の矢印ボタンからもpub get が実行できます。
②Cubitの作成
さて、今回僕たちがやりたいのはカウンターアプリでSetState によって状態更新(状態管理)している部分をcubit に置き換えるということです。
まずはcounter_cubit.dart というファイルに今回必要なCubit を作っていきます。
import 'package:flutter_bloc/flutter_bloc.dart'; class CounterCubit extends Cubit<int> { CounterCubit() : super(0); void increase() => emit(state + 1); void decrease() => emit(state - 1); }
③BlocProviderでCubitを渡す
Cubit の実装が終わったところで、こちらをmain.dart に渡します。その役割を担うのがBlocProvider です。
気をつけるべきは、Widget のツリー構造の中で、使用したい箇所より上の階層でCubit を渡すことです。
//... class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: BlocProvider( create: ((_) => CounterCubit()), child: MyHomePage(title: 'Flutter Demo Home Page')), ); } } //...
※(_) => CounterCubit() がわからない方
flutterでは引数を特に使わない場合、(_) とする慣習があります。ここには実際BuildContext が入っていますがCounterCubit()では使っていませんよね。そう分かると無名関数をアロー関数の形で書いていてだけです。
④BlocBuilderで状態更新時にビルドする
BulocBuilder は状態更新される度、つまりemit() が行われる度にBlocBuilder内のビルドを行い更新内容を画面に反映します。
//... @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), BlocBuilder<CounterCubit, int>( builder: ((context, state) => Text( '$state', style: Theme.of(context).textTheme.headline4, )), ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } //...
ここまでで状態管理の役割はSetState から Cubit に移りました。不要になったStateful Widget をStateless Widget に変更し、SetStateなどの余分な関数を削除しましょう。
import 'package:counter_cubit_likedemo/bloc/counter_cubit.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: BlocProvider( create: ((_) => CounterCubit()), child: const MyHomePage(title: 'Flutter Demo Home Page')), ); } } class MyHomePage extends StatelessWidget { const MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), BlocBuilder<CounterCubit, int>( builder: ((context, state) => Text( '$state', style: Theme.of(context).textTheme.headline4, )), ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, // エラー tooltip: 'Increment', child: const Icon(Icons.add), ), ); } }
SetStateの処理を削除したのでfloatingActionButton の_increamentCounter でエラーが起きていますね。この部分を修正しましょう。
⑤context.read<Cubit>(). で状態を更新する
context.read はcontextに入っているBlocProviderがcreateしたCubit() 、つまりインスタンスのデータを受け取り、Cubit にある関数を呼び出します。ここではCounterCubit のインスタンスを見にいき、increaseを呼び出すことでemit() が行われ
、状態が更新されます。
//... floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ FloatingActionButton( onPressed: context.read<CounterCubit>().increase, tooltip: 'Increase', child: const Icon(Icons.add), ), const SizedBox( height: 10, ), FloatingActionButton( onPressed: context.read<CounterCubit>().decrease, tooltip: 'Decrease', child: const Icon(Icons.remove), ), //...
⑥実装完了
これで全ての実装終了です。改めてmain.dart の完成形はこちらになります。
import 'package:counter_cubit_likedemo/bloc/counter_cubit.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: BlocProvider( create: ((_) => CounterCubit()), child: const MyHomePage(title: 'Flutter Demo Home Page')), ); } } class MyHomePage extends StatelessWidget { const MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), BlocBuilder<CounterCubit, int>( builder: ((context, state) => Text( '$state', style: Theme.of(context).textTheme.headline4, )), ), ], ), ), floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ FloatingActionButton( onPressed: context.read<CounterCubit>().increase, tooltip: 'Increase', child: const Icon(Icons.add), ), const SizedBox( height: 10, ), FloatingActionButton( onPressed: context.read<CounterCubit>().decrease, tooltip: 'Decrease', child: const Icon(Icons.remove), ), ], ), ); } }
まとめ
今回はカウンターアプリを使ってシンプルなcubitの使い方を紹介しました。
BlocBuilder, BlocProvider, context.read など扱いましたがまだまだ使い方がわかりにくいですよね。
こちらの記事でcubitの使い方をまとめていますのでもっと詳しく知りたい方は是非見て下さい。
コメント
コメント一覧 (5件)
[…] 【Flutter】カウンターアプリでCubit の状態管理を理解しよう この記事では、お馴染みのデフォルトカウンターアプリを題材にbloc/cubit […]
Helpful info. Lucky me I found your web site by accident, and I’m surprised why this coincidence did not came about earlier!
I bookmarked it.
Thanks very much!
I really glad to hear that.
Wow, fantastic blog layout! How long have you been blogging for?
you make blogging look easy. The overall look of your website is fantastic, let alone the content!
I’m really happy to hear that!
Special Thanks!