State Management
Flutter state management - intro
FlutterのFlutterの 状態管理(managementState) について学びます
Flutterアプリ内でデータ(状態)を更新、参照する際にどのような方式で実施するかを 状態管理(managementState) といいます。
これはFlutterに限らず、Webでよく使われるSPA(React、Angular、Vue.js)でも重要なものとなります。
従来のWebシステムは、サーバーと通信する度に画面がリフレッシュされ、画面に表示するデータ(状態)が全てリロードされため、状態管理は不要でした。FlutterやSPAのWebは画面がリフレッシュされないため、画面内で状態を保持しておく必要が出てきました。
ここで学ぶこと
- Flutter状態管理の基礎
- Flutterの代表的な状態管理ソリューション
1 Flutter状態管理の基礎
Flutterの状態管理の基礎は 公式サイト 状態管理 で記載されています
2 Flutterの代表的な状態管理ソリューション
代表的な状態管理ソリューションは 公式サイト 状態管理ソリューション にリストアップされています。
2.1 サマリー
状態管理ソリューションを全て知る必要がありません。実際によく使われるものをまとめます。
以下記載したパッケージはいずれも、公式が推薦 (Flutter Favorite )するものです。
Flutter 状態管理ソリューションの違い
| ソリューション | メリット | デメリット |
|---|---|---|
| Built-in State Management (setState) |
| 複雑な状態の管理には不向き |
| Provider |
|
|
| BLoC |
|
|
| Riverpod |
|
|
2.2 コード例
追加パッケージ無し Built-in State Management (StatefulWidget and InheritedWidget)
Widgeの中でsetStateメソッドで状態を更新します
class CounterWidget extends StatefulWidget { @override _CounterWidgetState createState() => _CounterWidgetState(); } class _CounterWidgetState extends State<CounterWidget> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return RaisedButton( onPressed: _incrementCounter, child: Text('Increment $_counter'), ); } }Providerパッケージを利用する例
ProviderはInheritedWidgetというFlutterの低レベルのクラスをラップして使いやすくしたものです。
class Counter with ChangeNotifier { int _count = 0; int get count => _count; void increment() { _count++; notifyListeners(); } } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (context) => Counter(), child: MaterialApp( home: Scaffold( appBar: AppBar(title: Text('Provider Example')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('You have pushed the button this many times:'), Consumer<Counter>( builder: (context, counter, child) => Text( '${counter.count}', style: Theme.of(context).textTheme.headline4, ), ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: () => context.read<Counter>().increment(), tooltip: 'Increment', child: Icon(Icons.add), ), ), ), ); } }BLoC (Business Logic Component)パッケージを利用する例
BLoCは状態と状態更新のロジックをUIから分離することができます。
class CounterBloc { final _counterStreamController = StreamController<int>(); Stream<int> get counterStream => _counterStreamController.stream; int _counter = 0; void increment() { _counterStreamController.sink.add(++_counter); } void dispose() { _counterStreamController.close(); } } class MyApp extends StatelessWidget { final _bloc = CounterBloc(); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text('BLoC Example')), body: Center( child: StreamBuilder<int>( stream: _bloc.counterStream, initialData: 0, builder: (context, snapshot) { return Text( 'Counter: ${snapshot.data}', style: Theme.of(context).textTheme.headline4, ); }, ), ), floatingActionButton: FloatingActionButton( onPressed: _bloc.increment, tooltip: 'Increment', child: Icon(Icons.add), ), ), ); } }Riverpodパッケージを利用する例
Riverpodは、Providerの改良版で、より機能が豊富で柔軟な使い方ができます。
final counterProvider = StateProvider<int>((ref) => 0); class MyApp extends ConsumerWidget { @override Widget build(BuildContext context, ScopedReader watch) { final counter = watch(counterProvider).state; return MaterialApp( home: Scaffold( appBar: AppBar(title: Text('Riverpod Example')), body: Center( child: Text( 'Counter: $counter', style: Theme.of(context).textTheme.headline4, ), ), floatingActionButton: FloatingActionButton( onPressed: () => context.read(counterProvider).state++, tooltip: 'Increment', child: Icon(Icons.add), ), ), ); } }