Menu
CoddyTech

选择状态的存放位置

本课属于 Coddy Flutter 学习旅程的“状态管理”单元。第 21 课,共 27 课。

设置屏幕上的深色模式开关可以切换,但界面并没有变暗。它通过 setState 保存了自己的值,只有这个开关能读取它。主题是在很远的 MyApp 中设置的。这个值属于整个应用。

应用中的每个值都需要有自己的归属。先想想谁会用到它:

  • 一个组件(输入框中的文本、打开的面板、标签页索引):在该组件中使用 setState。
  • 几个相邻的组件(一个按钮和旁边的标签):将状态提升到它们的父组件,再向下传递。
  • 许多组件或多个屏幕(用户、购物车、设置):使用 Provider 或 Riverpod 共享模型。

选 Provider 还是 Riverpod?两者都能完成任务。Provider 稍微简单一些,会把模型放在组件树中,并通过 context 查找。Riverpod 将提供者放在组件树之外,因此读取值不需要 context,而且提供者可以互相监听。为一个应用选定一种,并在整个应用中使用它。

这里适合使用 StateProvider:一个 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 在线编译器