Menu
CoddyTech

状態の置き場所を選ぶ

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,
challenge icon

チャレンジ

難しい

ダークモードの切り替えでアプリ全体が変わるようにします。

  • 先頭に 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;
              });
            },
          ),
        ],
      ),
    );
  }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

状態管理のすべてのレッスン

自分で練習してみよう: Flutterオンラインコンパイラ