Contents
状態管理とは
✅ 状態を持つ : 様々なデータを元にUIを表示・変更・描画できる仕組みがある
✅ アプリのUIを形作るデータをアプリの状態と呼ぶ
✅ 状態を扱いやすく管理する仕組み⇨状態管理という
✅ 状態管理をしないとデータの扱いが複雑になり、プログラム構築の難易度が上がってしまう
Riverpod
✅ StatefulWidgetとStateで状態からUIを作ることは可能だが、様々なWidgetが重なり合ったUIとなり状態が複雑化する可能性⇨状態を管理しきれないリスクあり
✅ 複雑な状態を簡単に管理できる状態管理パッケージ
Riverpodのインストール
Riverpodを導入する前に依存関係にあるパッケージのインストールが必要なので、
必ずRiverpod公式チュートリアルで確認しよう。
👇 今回は”hooks_riverpod”と”flutter_riverpod”を導入していきます。
⇨ flutter_riverpod(https://pub.dev/packages/flutter_riverpod)
⇨ hooks_riverpod(https://pub.dev/packages/hooks_riverpod)
1 2 3 4 |
// <pubspec.yaml> dependencies: flutter_riverpod: ^0.14.0+3 hooks_riverpod: ^0.14.0+4 |
1 2 |
// <terminal> [~] $ flutter pub get |
1 2 3 4 |
<.dart> import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; |
Riverpodの使い方
データを渡す・受け取る
Consumer
✅ 値を受け取るWidgetをProviderScope()の子Widgetとし、データの受け渡し可能な状態にする
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; final helloWorldProvider = Provider((_) => "Hello World!"); void main() { runApp(ProviderScope( child: MyApp(), )); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { // Consumerのコールバック経由でwatchという名のScopedReaderを使う return Consumer(builder: (context, watch, child) { final helloWorld = watch(helloWorldProvider); // // helloWorldProviderを見にいく return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( body: Center(child: Text(helloWorld)), ), ); }); } } |
ConsumerWidget
✅ ConsumerWidgetを継承するとbuildメソッドからScopedReaderが受け取れる
✅ Widget全体が再描画の対象範囲になるため、パフォーマンスを考慮する場合はConsumerを使って再描画の対象範囲を制限する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; final helloWorldProvider = Provider((_) => "Hello World!"); void main() { runApp(ProviderScope( child: MyApp(), )); } // ConsumerWidgetを継承するとbuildメソッドからScopedReaderが受け取れる class MyApp extends ConsumerWidget { @override Widget build(BuildContext context, ScopedReader watch) { final helloWorld = watch(helloWorldProvider); return MaterialApp( home: Scaffold( body: Center(child: Text(helloWorld)), ), ); } } |
context,read
✅ この書き方は値を検知できないため、ボタンのタップや画面遷移などそのタイミングでデータを参照したいときに使う(値の更新を検知する必要がない時には有効)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; final helloWorldProvider = Provider((_) => "Hello World!"); void main() { runApp(ProviderScope( child: MyApp(), )); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { // BuildContextのextensionを利用する。しかしProviderの値を検知できないので、 // 値の更新を検知する必要がない場合に有効 final helloWorld = context.read(helloWorldProvider); return MaterialApp( home: Scaffold( body: Center( child: Text(helloWorld), ), ), ); } } |
HookWidget/userProvider
✅ FlutterHooksはRiverpodのパッケージ開発者が開発しているため、今後採用していくと良さそう
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; final helloWorldProvider = Provider((_) => "Hello World!"); void main() { runApp(ProviderScope( child: MyApp(), )); } class MyApp extends HookWidget { @override Widget build(BuildContext context) { final helloWorld = useProvider(helloWorldProvider); return MaterialApp( home: Scaffold( body: Center(child: Text(helloWorld)), ), ); } } |
データを更新する
StateProvider
✅ 値が更新される可能性があり、その更新を検知し値を渡す場合はStateProviderを使う
✅ この方法は値の更新処理(count += 1)をWidget内に書くため処理が複雑になったときは厳しい
→StateNotifierなどを継承したクラスを定義し、それを値として渡す方法もある(後述)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; // StateProviderで更新可能な値を渡す final countStateProvider = StateProvider((_) => 0); void main() { runApp(ProviderScope( child: MyApp(), )); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: CountApp(), ); } } class CountApp extends HookWidget { @override Widget build(BuildContext context) { // watchと同じで値が更新されたらcountが自動的に反映される final count = useProvider(countStateProvider).state; return Scaffold( body: Center( child: Text("$count"), ), floatingActionButton: FloatingActionButton( onPressed: () => { // 更新したい値を代入 context.read(countStateProvider).state += 1, }, child: Icon(Icons.add), ), ); } } |
StateNotifierProvider
✅ StateNotifierを継承したクラス(CountState)を定義しその中に値の更新処理を記述してUIを分けてコーディングが可能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; final countStateProvider = StateNotifierProvider<CountState, int>((_) => CountState()); class CountState extends StateNotifier<int> { CountState() : super(0); void increment() => this.state += 1; } void main() { runApp(ProviderScope( child: MyApp(), )); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: CountApp(), ); } } class CountApp extends HookWidget { @override Widget build(BuildContext context) { final count = useProvider(countStateProvider); return Scaffold( body: Center( child: Text("$count"), ), floatingActionButton: FloatingActionButton( onPressed: () { context.read(countStateProvider.notifier).increment(); }, child: Icon(Icons.add), ), ); } } |
コメントを残す