第15章 - 状态管理进阶
嗨,朋友!我是长安。
在第7章,我们学习了用 setState 管理状态。但当应用变得复杂时,setState 就不够用了——状态需要在多个页面之间共享,业务逻辑和 UI 混在一起难以维护。
这一章,我们来学习更强大的状态管理方案:Provider 和 GetX。
🤔 为什么需要状态管理?
setState 的局限性
// 问题1:状态只能向下传递,不能向上或平级传递
class ParentPage extends StatefulWidget {
State<ParentPage> createState() => _ParentPageState();
}
class _ParentPageState extends State<ParentPage> {
int count = 0;
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $count'),
ChildA(count: count), // 可以向下传
ChildB(count: count), // 可以向下传
// 但如果 ChildA 想修改 count,怎么办?
// 需要把回调函数也传下去,层级多了就很麻烦
],
);
}
}
// 问题2:深层嵌套传递很痛苦
// 如果 count 需要传到第5层的组件,中间每一层都要写参数
状态管理解决的问题
- 跨组件共享状态 - 任何组件都能访问共享状态
- 状态和 UI 分离 - 业务逻辑独立,便于测试和维护
- 避免不必要的重建 - 只有依赖状态的组件才会重建
📦 Provider(官方推荐)
Provider 是 Flutter 官方推荐的状态管理方案,简单易学,功能强大。
1. 添加依赖
dependencies:
provider: ^6.1.1
2. 基础用法:ChangeNotifierProvider
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// 第1步:创建状态类(继承 ChangeNotifier)
class CounterProvider extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // 通知监听者更新
}
void decrement() {
_count--;
notifyListeners();
}
void reset() {
_count = 0;
notifyListeners();
}
}
// 第2步:在顶层提供状态
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CounterProvider(),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: const CounterPage(),
);
}
}
// 第3步:在任意子组件中使用
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Provider 示例')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('当前计数:'),
// 方式1:使用 Consumer
Consumer<CounterProvider>(
builder: (context, counter, child) {
return Text(
'${counter.count}',
style: const TextStyle(fontSize: 48),
);
},
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
// 方式2:使用 context.read(不监听变化,只调用方法)
onPressed: () => context.read<CounterProvider>().decrement(),
child: const Text('-'),
),
const SizedBox(width: 20),
ElevatedButton(
onPressed: () => context.read<CounterProvider>().increment(),
child: const Text('+'),
),
],
),
],
),
),
);
}
}
3. 读取状态的三种方式
// 方式1:Consumer - 推荐,精确控制重建范围
Consumer<CounterProvider>(
builder: (context, counter, child) {
return Text('${counter.count}');
},
)
// 方式2:context.watch - 监听变化,整个 build 方法会重建
Widget build(BuildContext context) {
final counter = context.watch<CounterProvider>();
return Text('${counter.count}');
}
// 方式3:context.read - 不监听变化,只读取一次(用于调用方法)
onPressed: () {
context.read<CounterProvider>().increment();
}
// 方式4:Provider.of - 传统方式
final counter = Provider.of<CounterProvider>(context);
final counter = Provider.of<CounterProvider>(context, listen: false);
watch vs read
context.watch<T>()= 监听变化,值变了会重建context.read<T>()= 只读一次,不监听变化- 一般在
build方法中用watch,在回调函数中用read
4. 多 Provider 组合
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => UserProvider()),
ChangeNotifierProvider(create: (_) => CartProvider()),
ChangeNotifierProvider(create: (_) => ThemeProvider()),
],
child: const MyApp(),
),
);
}
// 在组件中使用
class HomePage extends StatelessWidget {
Widget build(BuildContext context) {
final user = context.watch<UserProvider>();
final cart = context.watch<CartProvider>();
return Scaffold(
appBar: AppBar(
title: Text('欢迎,${user.name}'),
actions: [
Badge(
label: Text('${cart.itemCount}'),
child: IconButton(
icon: const Icon(Icons.shopping_cart),
onPressed: () {},
),
),
],
),
body: ...,
);
}
}
5. Selector - 精细控制重建
当 Provider 状态很多时,用 Selector 只监听需要的部分:
// UserProvider 有多个属性
class UserProvider extends ChangeNotifier {
String _name = '';
int _age = 0;
String _avatar = '';
String get name => _name;
int get age => _age;
String get avatar => _avatar;
void updateName(String name) {
_name = name;
notifyListeners();
}
void updateAge(int age) {
_age = age;
notifyListeners();
}
}
// 只监听 name 变化
Selector<UserProvider, String>(
selector: (_, provider) => provider.name,
builder: (_, name, __) {
return Text('姓名:$name');
},
)
// 只监听 age 变化
Selector<UserProvider, int>(
selector: (_, provider) => provider.age,
builder: (_, age, __) {
return Text('年龄:$age');
},
)
// 使用 context.select
final name = context.select<UserProvider, String>((p) => p.name);
6. 实战:用户登录状态管理
// lib/providers/auth_provider.dart
class AuthProvider extends ChangeNotifier {
User? _user;
bool _isLoading = false;
String? _error;
User? get user => _user;
bool get isLoggedIn => _user != null;
bool get isLoading => _isLoading;
String? get error => _error;
// 登录
Future<bool> login(String email, String password) async {
_isLoading = true;
_error = null;
notifyListeners();
try {
// 模拟 API 请求
await Future.delayed(const Duration(seconds: 2));
if (email == 'test@test.com' && password == '123456') {
_user = User(id: 1, name: '长安', email: email);
_isLoading = false;
notifyListeners();
return true;
} else {
_error = '邮箱或密码错误';
_isLoading = false;
notifyListeners();
return false;
}
} catch (e) {
_error = '登录失败:$e';
_isLoading = false;
notifyListeners();
return false;
}
}
// 登出
void logout() {
_user = null;
notifyListeners();
}
}
class User {
final int id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
}
// lib/pages/login_page.dart
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
Widget build(BuildContext context) {
final auth = context.watch<AuthProvider>();
return Scaffold(
appBar: AppBar(title: const Text('登录')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: _emailController,
decoration: const InputDecoration(
labelText: '邮箱',
hintText: 'test@test.com',
),
),
const SizedBox(height: 16),
TextField(
controller: _passwordController,
decoration: const InputDecoration(
labelText: '密码',
hintText: '123456',
),
obscureText: true,
),
if (auth.error != null) ...[
const SizedBox(height: 16),
Text(
auth.error!,
style: const TextStyle(color: Colors.red),
),
],
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: auth.isLoading
? null
: () async {
final success = await context.read<AuthProvider>().login(
_emailController.text,
_passwordController.text,
);
if (success && mounted) {
Navigator.pushReplacementNamed(context, '/home');
}
},
child: auth.isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('登录'),
),
),
],
),
),
);
}
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
}
🚀 GetX(功能最全)
GetX 是一个全能包:状态管理 + 路由管理 + 依赖注入 + 国际化,一个包搞定所有。
1. 添加依赖
dependencies:
get: ^4.6.6
2. 响应式状态管理
import 'package:flutter/material.dart';
import 'package:get/get.dart';
// 第1步:创建 Controller
class CounterController extends GetxController {
// .obs 让变量变成响应式
var count = 0.obs;
// 也可以这样定义
final RxInt count2 = 0.obs;
final RxString name = '长安'.obs;
final RxBool isLoading = false.obs;
final RxList<String> items = <String>[].obs;
void increment() => count++;
void decrement() => count--;
void reset() => count.value = 0;
}
// 第2步:使用 GetMaterialApp
void main() {
runApp(GetMaterialApp(
home: const CounterPage(),
));
}
// 第3步:在组件中使用
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
Widget build(BuildContext context) {
// 注入 Controller(Get.put 会自动管理生命周期)
final controller = Get.put(CounterController());
return Scaffold(
appBar: AppBar(title: const Text('GetX 示例')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('当前计数:'),
// Obx 自动监听响应式变量
Obx(() => Text(
'${controller.count}',
style: const TextStyle(fontSize: 48),
)),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: controller.decrement,
child: const Text('-'),
),
const SizedBox(width: 20),
ElevatedButton(
onPressed: controller.increment,
child: const Text('+'),
),
],
),
],
),
),
);
}
}
3. GetBuilder(非响应式)
如果不想用 .obs,可以用 GetBuilder:
class CounterController extends GetxController {
int count = 0;
void increment() {
count++;
update(); // 手动通知更新
}
}
// 使用 GetBuilder
GetBuilder<CounterController>(
init: CounterController(),
builder: (controller) {
return Text('${controller.count}');
},
)
4. 依赖注入
// 注册依赖
Get.put(CounterController()); // 立即创建
Get.lazyPut(() => CounterController()); // 延迟创建
Get.putAsync(() async => await ApiService()); // 异步创建
// 获取依赖
final controller = Get.find<CounterController>();
// 删除依赖
Get.delete<CounterController>();
// 使用 Bindings 集中管理依赖
class HomeBinding extends Bindings {
void dependencies() {
Get.lazyPut(() => HomeController());
Get.lazyPut(() => UserController());
}
}
// 在路由中使用 Binding
GetPage(
name: '/home',
page: () => const HomePage(),
binding: HomeBinding(),
)
5. 路由管理
// 基本导航(无需 context!)
Get.to(() => const DetailPage()); // 跳转
Get.toNamed('/detail'); // 命名路由跳转
Get.back(); // 返回
Get.off(() => const LoginPage()); // 跳转并移除当前页
Get.offAll(() => const HomePage()); // 清空所有路由
Get.offNamed('/home'); // 命名路由替换
// 传参
Get.to(() => const DetailPage(), arguments: {'id': 123});
// 接收参数
final args = Get.arguments;
// 命名路由传参
Get.toNamed('/detail', arguments: {'id': 123});
Get.toNamed('/detail?id=123');
// 接收参数
final id = Get.parameters['id'];
// 返回并传值
Get.back(result: 'success');
// 接收返回值
final result = await Get.to(() => const EditPage());
// 定义路由
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => const HomePage()),
GetPage(name: '/detail', page: () => const DetailPage()),
GetPage(
name: '/login',
page: () => const LoginPage(),
transition: Transition.fadeIn,
transitionDuration: const Duration(milliseconds: 300),
),
],
)
6. 便捷工具
// SnackBar(无需 context!)
Get.snackbar(
'标题',
'内容',
snackPosition: SnackPosition.BOTTOM,
duration: const Duration(seconds: 3),
);
// Dialog
Get.defaultDialog(
title: '提示',
middleText: '确定要删除吗?',
textConfirm: '确定',
textCancel: '取消',
onConfirm: () {
Get.back();
// 执行删除
},
);
// BottomSheet
Get.bottomSheet(
Container(
color: Colors.white,
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.camera),
title: const Text('拍照'),
onTap: () => Get.back(),
),
ListTile(
leading: const Icon(Icons.photo),
title: const Text('相册'),
onTap: () => Get.back(),
),
],
),
),
);
// 主题切换
Get.changeTheme(ThemeData.dark());
Get.isDarkMode; // 是否深色模式
// 语言切换
Get.updateLocale(const Locale('zh', 'CN'));
7. 实战:完整的 GetX 应用结构
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'GetX Demo',
theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
initialRoute: '/',
getPages: [
GetPage(
name: '/',
page: () => const HomePage(),
binding: HomeBinding(),
),
GetPage(
name: '/detail/:id',
page: () => const DetailPage(),
),
],
);
}
}
// lib/bindings/home_binding.dart
class HomeBinding extends Bindings {
void dependencies() {
Get.lazyPut(() => HomeController());
}
}
// lib/controllers/home_controller.dart
class HomeController extends GetxController {
final items = <String>[].obs;
final isLoading = false.obs;
void onInit() {
super.onInit();
loadData();
}
Future<void> loadData() async {
isLoading.value = true;
await Future.delayed(const Duration(seconds: 1));
items.value = ['Item 1', 'Item 2', 'Item 3'];
isLoading.value = false;
}
void addItem(String item) {
items.add(item);
}
void removeItem(int index) {
items.removeAt(index);
}
}
// lib/pages/home_page.dart
class HomePage extends StatelessWidget {
const HomePage({super.key});
Widget build(BuildContext context) {
final controller = Get.find<HomeController>();
return Scaffold(
appBar: AppBar(
title: const Text('首页'),
actions: [
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
Get.defaultDialog(
title: '添加',
content: TextField(
onSubmitted: (value) {
controller.addItem(value);
Get.back();
},
),
);
},
),
],
),
body: Obx(() {
if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator());
}
return ListView.builder(
itemCount: controller.items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(controller.items[index]),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => controller.removeItem(index),
),
onTap: () => Get.toNamed('/detail/$index'),
);
},
);
}),
);
}
}
// lib/pages/detail_page.dart
class DetailPage extends StatelessWidget {
const DetailPage({super.key});
Widget build(BuildContext context) {
final id = Get.parameters['id'];
return Scaffold(
appBar: AppBar(title: Text('详情 $id')),
body: Center(
child: Text('这是第 $id 项的详情'),
),
);
}
}
⚖️ Provider vs GetX 对比
| 特性 | Provider | GetX |
|---|---|---|
| 学习曲线 | 简单 | 简单 |
| 官方推荐 | ✅ 是 | ❌ 否 |
| 状态管理 | ✅ | ✅ |
| 路由管理 | ❌ 需要额外包 | ✅ 内置 |
| 依赖注入 | ❌ 需要额外包 | ✅ 内置 |
| 国际化 | ❌ 需要额外包 | ✅ 内置 |
| 代码量 | 较多 | 较少 |
| 测试友好 | ✅ 更好 | 一般 |
| 社区支持 | 很好 | 很好 |
选择建议
选 Provider:
- 追求官方标准
- 项目较大,需要更好的可测试性
- 团队熟悉 Provider
选 GetX:
- 追求开发速度
- 想要一个包搞定所有
- 个人项目或小团队
📝 小结
这一章我们学习了两种主流的状态管理方案:
Provider
ChangeNotifierProvider- 提供状态Consumer/context.watch- 监听状态context.read- 读取状态(不监听)Selector- 精细控制重建
GetX
.obs/Obx- 响应式状态Get.put/Get.find- 依赖注入Get.to/Get.toNamed- 路由导航Get.snackbar/Get.dialog- 便捷工具
状态管理最佳实践
- 分离关注点 - 把业务逻辑放在 Provider/Controller 中
- 最小化重建 - 用 Consumer/Selector/Obx 精确控制
- 合理划分状态 - 按功能模块划分多个 Provider/Controller
- 避免过度设计 - 简单场景用 setState 就够了
💪 练习题
- 用 Provider 实现一个购物车功能(添加、删除、计算总价)
- 用 GetX 实现一个用户登录/登出功能
- 对比两种方案的代码量和开发体验
🚀 下一步
掌握了状态管理后,下一章我们来学习 动画入门,让你的应用动起来!
由 编程指南 提供
