在 Flutter 应用中管理状态的 6 种方法(附代码示例)
在 Flutter 中如何管理状态
在本文中,我们将探讨 六种流行的 Flutter 应用状态管理 方法, 包括实际示例和最佳实践:
状态管理 是 Flutter 开发中最重要且最具争议的话题之一。它决定了您的应用如何处理数据变化并高效更新 UI。Flutter 为您提供多种管理状态的策略,从简单到高度可扩展。
Flutter 的状态管理策略包括:
- 🧱
setState()
— 内置的、最简单的方法 - 🏗️ InheritedWidget — Flutter 状态传播的基础
- 🪄 Provider — 大多数应用推荐的解决方案
- 🔒 Riverpod — Provider 的现代、编译安全的演变
- 📦 Bloc — 用于可扩展、企业级应用
- ⚡ GetX — 轻量级的一体化解决方案
1. 使用 setState()
— 基础知识
在 Flutter 中,管理状态的最简单方法是使用 StatefulWidget
中的内置 setState()
方法。
这种方法适用于 本地 UI 状态 —— 状态属于一个 widget,且不需要在应用中共享。
示例:使用 setState()
的计数器应用
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: CounterScreen());
}
}
class CounterScreen extends StatefulWidget {
@override
_CounterScreenState createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('setState Counter')),
body: Center(
child: Text('Count: $_count', style: TextStyle(fontSize: 24)),
),
floatingActionButton: FloatingActionButton(
onPressed: _increment,
child: Icon(Icons.add),
),
);
}
}
优点
- 非常容易实现
- 适用于本地或临时 UI 状态
- 没有外部依赖
缺点
- 不适合大型应用
- 在 widget 之间共享状态困难
- 逻辑与 UI 混合
使用场景:
- 原型设计或构建小部件
- 处理孤立的 UI 状态(例如,切换按钮、显示模态框)
2. InheritedWidget — Flutter 的基础
InheritedWidget
是 Flutter 用于向下传播数据的低级机制。大多数状态管理解决方案(包括 Provider)都是基于它的。
理解 InheritedWidget 有助于您掌握 Flutter 状态管理的内部工作原理。
示例:使用 InheritedWidget 的主题管理器
import 'package:flutter/material.dart';
// 持有主题数据的 InheritedWidget
class AppTheme extends InheritedWidget {
final bool isDarkMode;
final Function toggleTheme;
const AppTheme({
Key? key,
required this.isDarkMode,
required this.toggleTheme,
required Widget child,
}) : super(key: key, child: child);
// 获取 widget 树上最近的 AppTheme 的方法
static AppTheme? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<AppTheme>();
}
@override
bool updateShouldNotify(AppTheme oldWidget) {
return isDarkMode != oldWidget.isDarkMode;
}
}
// 管理状态变化的有状态包装器
class ThemeManager extends StatefulWidget {
final Widget child;
const ThemeManager({Key? key, required this.child}) : super(key: key);
@override
_ThemeManagerState createState() => _ThemeManagerState();
}
class _ThemeManagerState extends State<ThemeManager> {
bool _isDarkMode = false;
void _toggleTheme() {
setState(() {
_isDarkMode = !_isDarkMode;
});
}
@override
Widget build(BuildContext context) {
return AppTheme(
isDarkMode: _isDarkMode,
toggleTheme: _toggleTheme,
child: widget.child,
);
}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ThemeManager(
child: Builder(
builder: (context) {
final theme = AppTheme.of(context);
return MaterialApp(
theme: theme!.isDarkMode ? ThemeData.dark() : ThemeData.light(),
home: HomeScreen(),
);
},
),
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = AppTheme.of(context);
return Scaffold(
appBar: AppBar(title: Text('InheritedWidget Theme')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Current Theme: ${theme!.isDarkMode ? "Dark" : "Light"}',
style: TextStyle(fontSize: 20),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => theme.toggleTheme(),
child: Text('Toggle Theme'),
),
],
),
),
);
}
}
优点
- 内置于 Flutter —— 无需外部依赖
- 高效的 widget 重建
- 理解其他解决方案的基础
- 对传播逻辑有直接控制
缺点
- 冗长的样板代码
- 需要包装的 StatefulWidget
- 容易出错
- 不适合初学者
使用场景:
- 学习 Flutter 状态管理的内部工作原理
- 构建自定义状态管理解决方案
- 需要对状态传播有非常具体的控制
3. Provider — Flutter 推荐的解决方案
当您的应用状态需要在多个 widget 之间共享时,Provider 就派上用场了。
Provider 基于 控制反转 —— widget 不拥有状态,而是由 provider 暴露出来供其他 widget 使用。Flutter 团队官方推荐它用于 中型 应用。
设置
将以下内容添加到您的 pubspec.yaml
:
dependencies:
provider: ^6.0.5
示例:使用 Provider 的计数器应用
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: MyApp(),
),
);
}
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: CounterScreen());
}
}
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counter = context.watch<CounterModel>();
return Scaffold(
appBar: AppBar(title: Text('Provider Counter')),
body: Center(
child: Text('Count: ${counter.count}', style: TextStyle(fontSize: 24)),
),
floatingActionButton: FloatingActionButton(
onPressed: context.read<CounterModel>().increment,
child: Icon(Icons.add),
),
);
}
}
优点
- 反应式且高效的更新
- UI 和逻辑的清晰分离
- 文档齐全且社区支持良好
缺点
- 比
setState()
稍微多一些样板代码 - 嵌套的 provider 可能变得复杂
使用场景:
- 需要在多个 widget 之间共享状态
- 想使用反应式模式而不增加复杂度
- 应用规模已超出原型阶段
4. Riverpod — Provider 的现代演变
Riverpod 是 Provider 的完全重写,去除了对 BuildContext 的依赖并增加了编译时安全性。它旨在解决 Provider 的限制,同时保持相同的哲学。
设置
将以下内容添加到您的 pubspec.yaml
:
dependencies:
flutter_riverpod: ^2.4.0
示例:使用 Riverpod 的用户资料
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 模型
class UserProfile {
final String name;
final int age;
UserProfile({required this.name, required this.age});
UserProfile copyWith({String? name, int? age}) {
return UserProfile(
name: name ?? this.name,
age: age ?? this.age,
);
}
}
// 状态通知器
class ProfileNotifier extends StateNotifier<UserProfile> {
ProfileNotifier() : super(UserProfile(name: 'Guest', age: 0));
void updateName(String name) {
state = state.copyWith(name: name);
}
void updateAge(int age) {
state = state.copyWith(age: age);
}
}
// 提供器
final profileProvider = StateNotifierProvider<ProfileNotifier, UserProfile>((ref) {
return ProfileNotifier();
});
// 计算提供器
final greetingProvider = Provider<String>((ref) {
final profile = ref.watch(profileProvider);
return 'Hello, ${profile.name}! You are ${profile.age} years old.';
});
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: ProfileScreen());
}
}
class ProfileScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final profile = ref.watch(profileProvider);
final greeting = ref.watch(greetingProvider);
return Scaffold(
appBar: AppBar(title: Text('Riverpod Profile')),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(greeting, style: TextStyle(fontSize: 20)),
SizedBox(height: 30),
TextField(
decoration: InputDecoration(labelText: 'Name'),
onChanged: (value) {
ref.read(profileProvider.notifier).updateName(value);
},
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Age: ${profile.age}', style: TextStyle(fontSize: 18)),
SizedBox(width: 20),
ElevatedButton(
onPressed: () {
ref.read(profileProvider.notifier).updateAge(profile.age + 1);
},
child: Text('+'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
if (profile.age > 0) {
ref.read(profileProvider.notifier).updateAge(profile.age - 1);
}
},
child: Text('-'),
),
],
),
],
),
),
);
}
}
优点
- 不依赖 BuildContext —— 任何地方都可以访问状态
- 编译时安全性 —— 在运行时之前捕获错误
- 更好的可测试性
- 强大的状态组合
- 没有内存泄漏
缺点
- 与传统 Flutter 模式不同 API
- 学习曲线比 Provider 更陡峭
- 社区较小(但正在快速增长)
使用场景:
- 开始新的 Flutter 项目
- 想要类型安全和编译时保证
- 复杂的状态依赖和组合
- 开发中型到大型规模的应用
5. BLoC(业务逻辑组件)— 用于企业应用
BLoC 模式 是一种更高级的架构,它使用 流 完全将业务逻辑与 UI 分离。
它非常适合 大型或企业级应用,其中可预测和可测试的状态转换是必不可少的。
设置
将此依赖项添加到您的项目中:
dependencies:
flutter_bloc: ^9.0.0
示例:使用 BLoC 的计数器应用
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
// 事件
abstract class CounterEvent {}
class Increment extends CounterEvent {}
// BLoC
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<Increment>((event, emit) => emit(state + 1));
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterBloc(),
child: MaterialApp(home: CounterScreen()),
);
}
}
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Bloc Counter')),
body: Center(
child: BlocBuilder<CounterBloc, int>(
builder: (context, count) {
return Text('Count: $count', style: TextStyle(fontSize: 24));
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.read<CounterBloc>().add(Increment()),
child: Icon(Icons.add),
),
);
}
}
优点
- 对复杂应用可扩展且可维护
- 层次清晰的分离
- 易于单元测试
缺点
- 更多样板代码
- 学习曲线更陡峭
使用场景:
- 构建企业或长期项目
- 需要可预测和可测试的逻辑
- 多个开发人员在不同应用模块上工作
6. GetX — 一体化轻量级解决方案
GetX 是一个极简但强大的状态管理解决方案,还包括路由、依赖注入和实用工具。它以所有解决方案中样板代码最少而著称。
设置
将以下内容添加到您的 pubspec.yaml
:
dependencies:
get: ^4.6.5
示例:使用 GetX 的购物清单
import 'package:flutter/material.dart';
import 'package:get/get.dart';
// 控制器
class ShoppingController extends GetxController {
// 可观察列表
var items = <String>[].obs;
// 可观察计数器
var totalItems = 0.obs;
void addItem(String item) {
if (item.isNotEmpty) {
items.add(item);
totalItems.value = items.length;
}
}
void removeItem(int index) {
items.removeAt(index);
totalItems.value = items.length;
}
void clearAll() {
items.clear();
totalItems.value = 0;
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'GetX Shopping List',
home: ShoppingScreen(),
);
}
}
class ShoppingScreen extends StatelessWidget {
// 初始化控制器
final ShoppingController controller = Get.put(ShoppingController());
final TextEditingController textController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GetX Shopping List'),
actions: [
Obx(() => Padding(
padding: EdgeInsets.all(16),
child: Center(
child: Text(
'Items: ${controller.totalItems}',
style: TextStyle(fontSize: 18),
),
),
)),
],
),
body: Column(
children: [
Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: TextField(
controller: textController,
decoration: InputDecoration(
hintText: 'Enter item name',
border: OutlineInputBorder(),
),
),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
controller.addItem(textController.text);
textController.clear();
},
child: Icon(Icons.add),
),
],
),
),
Expanded(
child: Obx(() {
if (controller.items.isEmpty) {
return Center(
child: Text('No items in your list'),
);
}
return ListView.builder(
itemCount: controller.items.length,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(child: Text('${index + 1}')),
title: Text(controller.items[index]),
trailing: IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () => controller.removeItem(index),
),
);
},
);
}),
),
if (controller.items.isNotEmpty)
Padding(
padding: EdgeInsets.all(16),
child: ElevatedButton(
onPressed: () {
Get.defaultDialog(
title: 'Clear List',
middleText: 'Are you sure you want to clear all items?',
textConfirm: 'Yes',
textCancel: 'No',
onConfirm: () {
controller.clearAll();
Get.back();
},
);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
minimumSize: Size(double.infinity, 50),
),
child: Text('Clear All'),
),
),
],
),
);
}
}
优点
- 最少的样板代码 —— 开发速度最快
- 一体化解决方案(状态、路由、DI、小吃条、对话框)
- 不需要 BuildContext
- 非常轻量且快速
- 易于学习
缺点
- 相比 Provider/BLoC,不那么“Flutter 风格”
- 如果不谨慎,可能导致耦合紧密
- 生态系统比 Provider 小
- 一些开发人员觉得它“太神奇”
使用场景:
- 快速原型设计或 MVP 开发
- 小型到中型应用
- 想要最少样板代码
- 团队更喜欢简单性而非严格架构
选择合适的状态管理解决方案
解决方案 | 复杂度 | 样板代码 | 学习曲线 | 最佳适用 |
---|---|---|---|---|
🧱 setState() |
低 | 最小 | 容易 | 简单本地状态、原型 |
🏗️ InheritedWidget |
中等 | 高 | 中等 | 学习 Flutter 内部机制、自定义解决方案 |
🪄 Provider |
低-中等 | 低 | 容易 | 大多数应用、共享状态 |
🔒 Riverpod |
中等 | 低-中等 | 中等 | 现代应用、类型安全 |
📦 BLoC |
高 | 高 | 陡峭 | 企业应用、复杂业务逻辑 |
⚡ GetX |
低 | 最小 | 容易 | 快速开发、MVPs |
快速决策指南:
应用复杂度 | 推荐方法 | 示例用例 |
---|---|---|
🪶 简单 | setState() 或 GetX |
切换按钮、动画、小部件 |
⚖️ 中等 | Provider 或 Riverpod |
共享主题、用户设置、数据缓存 |
🏗️ 复杂 | BLoC 或 Riverpod |
电子商务、聊天应用、金融仪表板 |
最后想法
在 Flutter 中,没有一种适用于所有情况的状态管理方法。
实用方法如下:
- 从小开始,使用
setState()
进行简单的小部件和原型开发 - 学习基础知识,使用 InheritedWidget 来了解 Flutter 内部的工作原理
- 随着应用增长,使用 Provider 来管理共享状态
- 对于新项目,考虑 Riverpod,如果类型安全和现代模式很重要
- 对于大型企业应用,采用 BLoC,如果业务逻辑复杂
- 如果快速开发和最少样板代码是优先事项,尝试 GetX
关键要点:
✅ 不要过度设计 —— 在需要更多功能之前使用 setState()
✅ Provider 和 Riverpod 是大多数应用的绝佳选择
✅ BLoC 在大型团队和复杂应用中表现出色
✅ GetX 在速度方面很出色,但要注意耦合
✅ 理解 InheritedWidget 有助于掌握任何解决方案
关键是 平衡简单性、可扩展性和可维护性,并根据您的具体需求、团队专业知识和项目要求选择合适的工具。