はじめに
今回は、Flutterで簡単に状態管理をする手法について、備忘録的に書いておく記事です。
内容が間違っている場合、もっといいやり方がある場合はコメントにてお願いします。
今回の環境
- Flutter 3.10.1
- flutter_riverpod 2.4.10
- riverpod_generator 2.3.11
- riverpod_annotation 2.3.4
- freezed 2.5.2
- freezed_annotation 2.4.1
- build_runner 2.4.9
今回の記事でやること
以下の図のような状態管理をします。
viewとstateが1対1(または1対多)で紐ついています。
controllerはviewModelを言い換えても良いです。modelから作られたインスタンスの状態を返したり、インスタンスの状態を変更するメソッドもcontrollerが持ちます。
実際のコード
viewのコード
import 'package:advanced_search_2/ui/state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class TestPage extends StatelessWidget {
const TestPage({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: "Riverpod"),
);
}
}
class MyHomePage extends ConsumerWidget { // riverpodを使うためConsumerWidget
const MyHomePage({super.key, required this.title});
final String title;
@override
Widget build(BuildContext context, WidgetRef ref) {
// ↓ contollerの状態やメソッドを呼び出すためのコード ↓
final counter =
ref.watch(testPageStateProvider.select((value) => value.counter));
final incrementCounter =
ref.read(testPageStateProvider.notifier).incrementCounter;
final decrementCounter =
ref.read(testPageStateProvider.notifier).decrementCounter;
final doubleCounter =
ref.read(testPageStateProvider.notifier).doubleCounter;
final clearCounter = ref.read(testPageStateProvider.notifier).clearCounter;
// ↑ contollerの状態やメソッドを呼び出すためのコード ↑
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$counter',
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
incrementCounter();
},
child: const Text("increment")),
ElevatedButton(
onPressed: () => decrementCounter(),
child: const Text("decrement")),
ElevatedButton(
onPressed: () => doubleCounter(), child: const Text("double")),
ElevatedButton(
onPressed: () => clearCounter(), child: const Text("clear")),
],
),
),
);
}
}
controller・modelのコード
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'state.freezed.dart';
part 'state.g.dart';
// 管理する状態とその初期値を書きます
@freezed
class TestState with _$TestState {
const factory TestState({
required int counter,
required String title,
required String message,
String? description,
}) = _TestState;
}
@riverpod
final class TestPageState extends _$TestPageState {
@override
TestState build() {
init();
return const TestState(
counter: 0,
title: "RIVERPOD",
message: 'You have pushed the button this many times:',
);
}
// 状態を更新するメソッドを書きます
void incrementCounter() {
state = state.copyWith(counter: state.counter + 1);
}
void decrementCounter() {
state = state.copyWith(counter: state.counter - 1);
}
void doubleCounter() {
state = state.copyWith(counter: state.counter * 2);
}
void clearCounter() {
state = state.copyWith(counter: 0);
}
}
dart run build_runner build
の実行を忘れずに!
以上のコードで基本的な状態管理は可能です。
次はオプション的な記述を紹介します。
.autoDispose
を無効化
riverpod v2 以降では、autoDisposeがデフォルトで付与されています。
これを無効化(コントローラーの状態を破棄させない)するためには、明示的に書いてあげる必要があります。
- @riverpod
+ @Riverpod(keepAlive: true)
final class TestPageState extends _$TestPageState {
こうすることで、ページ遷移などで状態が破棄されることはなくなります。
Provider生成時に処理を実行する
今回の例で言うと、counterの値をmodelの初期値と違う値にしたい場合。
以下のような書き方をすることで、Provider生成時に処理を実行することができます。
final class TestPageState extends _$TestPageState {
@override
TestState build() {
state = const TestState(
counter: 0,
title: "RIVERPOD",
message: 'You have pushed the button this many times:',
);
init();
return state;
}
void init() {
state = state.copyWith(counter: 5);
}
// ~~~~~~ 省略 ~~~~~~
こうすることで、stateをinit()
で書き換えた、counter: 5
を初期値とすることができます。
非同期の処理を挟みたい場合はAsyncNotifierProvider
を使用する必要があります(多分)。
おわりに
お読みいただきありがとうございました!
Riverpodはv1しか触ってこなかったので、違和感がすごいですね...
riverpodを使用した状態管理はググるのがむずかしいです。(v1,v2やannotation、fooksの使用不使用などなど...)
riverpodのGitHubでissueやDiscussionsを掘るのが正解な気がします。