状態の置き場所を選ぶ
CoddyのFlutterジャーニー「状態管理」セクションの一部。レッスン 21/27。
設定画面のダークモード切り替えスイッチは動きますが、何も暗くなりません。setStateで値を保持していますが、その値を参照できるのはスイッチだけです。テーマは離れた場所にあるMyAppで設定されています。この値はアプリ全体に属します。
アプリ内のすべての値には、置き場所が必要です。誰がその値を使うのかを考えてみましょう。
- 1つのウィジェット(フィールドのテキスト、開いているパネル、タブのインデックス):そのウィジェットで
setStateを使います。 - 近くにあるいくつかのウィジェット(ボタンとその隣のラベル):親ウィジェットに状態を持ち上げ、子ウィジェットに渡します。
- 多くのウィジェットや複数の画面(ユーザー、カート、設定):ProviderまたはRiverpodを使った共有モデルを使用します。
ProviderとRiverpod、どちらを選べばよいでしょうか?どちらも役に立ちます。Providerは少しシンプルで、モデルをウィジェットツリーに配置し、contextを通じて見つけます。Riverpodではプロバイダーがツリーの外にあるため、値を使うのにcontextは不要で、プロバイダー同士が互いを監視できます。アプリでどちらか一方を選び、全体で使いましょう。
ここではStateProviderが適しています。1つのboolをMyAppが読み取ってテーマを選び、スイッチが値を変更します。
final darkModeProvider = StateProvider((ref) => false);
// MyApp 内では、今は ConsumerWidget:
themeMode: ref.watch(darkModeProvider) ? ThemeMode.dark : ThemeMode.light,チャレンジ
難しいダークモードの切り替えでアプリ全体が変わるようにします。
- 先頭に
final darkModeProvider = StateProvider((ref) => false);を追加します。 MyAppをConsumerWidgetに変更し、themeModeがプロバイダーに従うようにします。SettingsScreenをConsumerWidgetに変更します。スイッチにプロバイダーの値を表示し、変更時にその値を設定します。
自分で試してみよう
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: ThemeMode.light,
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Notes'),
actions: [
IconButton(
icon: const Icon(Icons.settings),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SettingsScreen()),
);
},
),
],
),
body: const Center(child: Text('No notes yet')),
);
}
}
class SettingsScreen extends StatefulWidget {
const SettingsScreen({super.key});
@override
State<SettingsScreen> createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> {
bool dark = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Settings')),
body: ListView(
children: [
SwitchListTile(
title: const Text('Dark mode'),
value: dark,
onChanged: (value) {
setState(() {
dark = value;
});
},
),
],
),
);
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
状態管理のすべてのレッスン
自分で練習してみよう: Flutterオンラインコンパイラ