前言
本系列是实战课程文章,通过一个完整的 Flutter 项目,上手 Flutter 开发,其中会涉及到路由、网络、状态管理等开发中需要用到的基础知识,通过这些系列的文章讲解,可以快速上手一个 Flutter 项目。这篇文章,我们来谈谈状态管理。说说什么是状态管理,为什么需要状态管理,这常常是困扰新手上路的一个问题。今天我们就来用实际的例子来聊聊如何使用状态管理,在实践当中进行学习。
什么是状态管理
状态是指用户界面(UI)显示的数据,它可能随着用户操作或应用逻辑的变化而发生改变,而状态管理就是如何追踪、更新这些数据,并让 UI 保持同步。例如:一个购物车应用。用户可以添加商品到购物车,删除商品,并查看总价。这里的状态包括:商品列表、购物车中的商品数量、总价这些在用户操作的时候界面都会发生相应的变化,如果没有一个清晰的状态管理方式,数据更新可能会变得难以维护,UI 和数据之间容易失去同步。
Flutter 提供了多种方式来管理状态,分为两大类:
- 本地状态管理(简单应用):适用于单个组件或页面内的状态。
- 全局状态管理(复杂应用):适用于需要在多个组件之间共享状态的情况。
我们现在以 BiliVideoDown
项目当中的暗黑模式切换谈谈当中所运用到的状态管理。
在这里,切换暗黑模式的按钮和其他界面在不同的组件当中,其他界面的组件是如何知道切换到了暗黑模式,根据暗黑模式的状态改变自身的主题颜色呢?这就是一个典型的状态管理的场景,状态统一进行管理,可以理解统一放在内存当中的某个地方,各个组件在需要的时候读取这个状态即可。下面就来看看如何使用 Riverpod
进行状态的管理。关于 Riverpod
,这里就不多做介绍了,Riverpod 是 Flutter 生态中一个现代化的状态管理库,相比于 Provider,它更加灵活、强大,并提供了一些新的特性,让开发状态管理更加简洁高效。
通过主题色切换理解状态管理
理解 Riverpod 当中的核心概念:
- 状态提供者:用于存储和管理状态
- 状态消费者:用于从提供者中读取或更新状态。
引入 Riverpod
首先,在 pubspec.yaml
中添加 Riverpod 依赖:
dart pub add riverpod
启用 Riverpod
在应用的根组件中使用 ProviderScope 包裹应用,启用 Riverpod 状态管理功能:
void main() async {
await init();
runApp(const ProviderScope(child: MyApp()));
}
定义 ThemeService
来管理 ThemeState
,并通过 StateNotifierProvider
暴露状态:
@MappableClass()
class ThemeState with ThemeStateMappable {
final bool isDark;
final ThemeMode themeMode;
const ThemeState({
required this.isDark,
required this.themeMode,
});
}
final themeProvider = StateNotifierProvider<ThemeService, ThemeState>((ref) {
final themeService = ThemeService();
themeService.init();
return themeService;
});
class ThemeService extends StateNotifier<ThemeState> {
ThemeService()
: super(const ThemeState(isDark: false, themeMode: ThemeMode.light));
// Initialize ThemeState
void init() {
bool isDark = SpUtil.getBool(SpKey.isDarkTheme) ?? false;
state = state.copyWith(isDark: isDark, themeMode: getThemeMode(isDark));
}
ThemeMode getThemeMode(bool isDark) {
return isDark ? ThemeMode.dark : ThemeMode.light;
}
void switchThemeMode() async {
bool isDark = !state.isDark;
state = state.copyWith(isDark: isDark, themeMode: getThemeMode(isDark));
await SpUtil.putBool(SpKey.isDarkTheme, isDark);
}
}
创建一个主题切换按钮,我们通过 ConsumerWidget
或 WidgetRef
在界面组件中访问和使用状态:
class ThemeSwitchIcon extends ConsumerWidget {
const ThemeSwitchIcon({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final appThemeProvider = ref.watch(themeProvider);
return MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
ref.read(themeProvider.notifier).switchThemeMode();
},
child: Padding(
padding: const EdgeInsets.only(bottom: 16, top: 16),
child: Icon(
appThemeProvider.isDark
? Icons.light_mode_outlined
: Icons.dark_mode_outlined,
color: appThemeProvider.isDark ? Colors.white : Colors.black,
),
),
),
);
}
}
根组件监听状态,让主题模式动态影响应用的全局主题:
class MyApp extends ConsumerWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final appThemeProvider = ref.watch(themeProvider);
return MaterialApp.router(
debugShowCheckedModeBanner: false,
routerConfig: routerConfig,
theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.light,
),
darkTheme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.dark,
),
themeMode: appThemeProvider.themeMode,
);
}
}
总结:
- 状态分离:通过
ThemeState
定义状态模型,数据与逻辑分离。- 全局共享:利用
StateNotifierProvider
,全局共享主题状态。- 界面与状态解耦:UI 通过 Riverpod 监听状态变化,自动响应更新。