在 Flutter 应用中,状态管理是一个核心概念。状态是指在应用程序生命周期中可能发生变化的数据。当状态发生变化时,UI 需要随之更新以反映这些变化。有效的状态管理策略对于构建可维护、可扩展和高性能的 Flutter 应用至关重要。
Flutter 本身提供了 StatefulWidget 来管理局部状态,但对于跨多个 Widget 共享的状态或复杂的状态逻辑,我们需要更强大的状态管理解决方案。目前 Flutter 社区有多种状态管理方案,其中 Provider 和 BLoC(Business Logic Component)是两种非常流行且功能强大的模式。
1. Provider (提供者)
Provider 是 Flutter 官方推荐的状态管理方案之一,它基于 InheritedWidget,但提供了更简洁、更易于使用的方式来管理和访问状态。Provider 的核心思想是“提供”数据给 Widget 树中的后代 Widget,并允许这些 Widget 在数据变化时重建。
Provider:最基本的提供者,用于提供一个值,当值改变时,依赖它的 Widget 会重建。ChangeNotifierProvider:用于提供一个 ChangeNotifier 对象。当 ChangeNotifier 调用 notifyListeners() 时,依赖它的 Widget 会重建。Consumer:一个 Widget,用于监听 Provider 提供的状态变化,并在状态变化时重建自身。Selector:类似于 Consumer,但允许你只监听状态的特定部分,从而进行更细粒度的重建。read 和 watch:context.read<T>():用于获取 Provider 提供的状态,但不会监听状态变化。适用于一次性读取或在事件回调中使用。context.watch<T>():用于获取 Provider 提供的状态,并监听状态变化。当状态变化时,使用 watch 的 Widget 会重建。示例:使用 ChangeNotifierProvider 管理计数器状态
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// 1. 定义一个 ChangeNotifier 类来管理状态
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // 通知所有监听器状态已改变
}
}
class ProviderExampleScreen extends StatelessWidget {
const ProviderExampleScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Provider 状态管理示例'),
),
body:
// 2. 使用 ChangeNotifierProvider 提供 Counter 实例
ChangeNotifierProvider(
create: (context) => Counter(),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'你点击了按钮这么多次:',
),
// 3. 使用 Consumer 监听 Counter 的变化并显示
Consumer<Counter>(
builder: (context, counter, child) {
return Text(
'${counter.count}',
style: Theme.of(context).textTheme.headlineMedium,
);
},
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// 4. 通过 context.read<Counter>() 获取 Counter 实例并调用方法
context.read<Counter>().increment();
},
child: const Text('增加计数'),
),
const SizedBox(height: 20),
// 5. 使用 Selector 监听特定部分的状态
Selector<Counter, bool>(
selector: (context, counter) => counter.count % 2 == 0,
builder: (context, isEven, child) {
return Text(
isEven ? '当前计数是偶数' : '当前计数是奇数',
style: TextStyle(color: isEven ? Colors.green : Colors.red),
);
},
),
],
),
),
),
);
}
}
void main() {
runApp(const MaterialApp(home: ProviderExampleScreen()));
}
2. BLoC (Business Logic Component)
BLoC 模式由 Google 提出,旨在将业务逻辑与 UI 分离,使代码更易于测试、复用和理解。BLoC 的核心思想是使用 Stream 来处理输入(Events)和输出(States)。
Stream 或 StreamController 暴露给 UI。flutter_bloc 包:一个流行的 Flutter 包,提供了 Bloc 和 Cubit 类以及 BlocProvider、BlocBuilder、BlocListener 等 Widget,极大地简化了 BLoC 模式的实现。示例:使用 flutter_bloc 管理计数器状态
首先,在 pubspec.yaml 中添加 flutter_bloc 依赖:
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.1.3 # 最新版本可能不同
然后运行 flutter pub get。
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
// 1. 定义 Events
abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
// 2. 定义 States
class CounterState {
final int count;
CounterState(this.count);
@override
List<Object> get props => [count]; // 用于比较状态是否相同
}
// 3. 定义 BLoC
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterState(0)) {
// 注册事件处理器
on<Increment>((event, emit) {
emit(CounterState(state.count + 1)); // 发出新的状态
});
on<Decrement>((event, emit) {
emit(CounterState(state.count - 1)); // 发出新的状态
});
}
}
class BlocExampleScreen extends StatelessWidget {
const BlocExampleScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BLoC 状态管理示例'),
),
body:
// 4. 使用 BlocProvider 提供 BLoC 实例
BlocProvider(
create: (context) => CounterBloc(),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'你点击了按钮这么多次:',
),
// 5. 使用 BlocBuilder 监听状态变化并显示
BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text(
'${state.count}',
style: Theme.of(context).textTheme.headlineMedium,
);
},
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
// 6. 通过 context.read<CounterBloc>() 获取 BLoC 实例并添加事件
context.read<CounterBloc>().add(Increment());
},
child: const Text('增加'),
),
const SizedBox(width: 20),
ElevatedButton(
onPressed: () {
context.read<CounterBloc>().add(Decrement());
},
child: const Text('减少'),
),
],
),
const SizedBox(height: 20),
// 7. 使用 BlocListener 监听状态变化并执行副作用 (如显示 SnackBar)
BlocListener<CounterBloc, CounterState>(
listener: (context, state) {
if (state.count % 5 == 0) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('计数达到 ${state.count}!')),
);
}
},
child: const SizedBox.shrink(), // BlocListener 通常不需要显示 UI
),
],
),
),
),
);
}
}
void main() {
runApp(const MaterialApp(home: BlocExampleScreen()));
}
状态管理是 Flutter 应用开发中不可避免的话题。选择合适的状态管理方案取决于项目的规模、复杂度和团队偏好。Provider 适用于中小型应用或简单的状态共享,而 BLoC 则更适合大型、复杂的应用,它强制了业务逻辑与 UI 的分离,提高了代码的可测试性和可维护性。
案例:一个简单的待办事项应用 (结合 Provider 和 BLoC 的思想)
我们将创建一个简单的待办事项应用,演示如何使用 ChangeNotifierProvider 来管理待办事项列表的状态,并结合一些 BLoC 的思想,将添加/删除待办事项的逻辑封装在 ChangeNotifier 中。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// 待办事项模型
class Todo {
String id;
String title;
bool isCompleted;
Todo({required this.id, required this.title, this.isCompleted = false});
// 复制构造函数,用于创建新的 Todo 实例(不可变性)
Todo copyWith({String? id, String? title, bool? isCompleted}) {
return Todo(
id: id ?? this.id,
title: title ?? this.title,
isCompleted: isCompleted ?? this.isCompleted,
);
}
}
// 待办事项管理类 (ChangeNotifier)
class TodoListNotifier with ChangeNotifier {
final List<Todo> _todos = [];
List<Todo> get todos => List.unmodifiable(_todos); // 返回不可修改的列表
void addTodo(String title) {
if (title.trim().isEmpty) return;
_todos.add(Todo(id: DateTime.now().toIso8601String(), title: title));
notifyListeners();
}
void toggleTodoCompletion(String id) {
final index = _todos.indexWhere((todo) => todo.id == id);
if (index != -1) {
_todos[index] = _todos[index].copyWith(isCompleted: !_todos[index].isCompleted);
notifyListeners();
}
}
void removeTodo(String id) {
_todos.removeWhere((todo) => todo.id == id);
notifyListeners();
}
}
class TodoAppScreen extends StatelessWidget {
const TodoAppScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('待办事项'),
),
body:
// 提供 TodoListNotifier 实例
ChangeNotifierProvider(
create: (context) => TodoListNotifier(),
child: Column(
children: <Widget>[
_AddTodoInput(), // 添加待办事项输入框
Expanded(
// 监听 TodoListNotifier 的变化并构建列表
child: Consumer<TodoListNotifier>(
builder: (context, todoListNotifier, child) {
return ListView.builder(
itemCount: todoListNotifier.todos.length,
itemBuilder: (context, index) {
final todo = todoListNotifier.todos[index];
return ListTile(
title: Text(
todo.title,
style: TextStyle(
decoration: todo.isCompleted ? TextDecoration.lineThrough : null,
color: todo.isCompleted ? Colors.grey : Colors.black,
),
),
leading: Checkbox(
value: todo.isCompleted,
onChanged: (bool? newValue) {
todoListNotifier.toggleTodoCompletion(todo.id);
},
),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
todoListNotifier.removeTodo(todo.id);
},
),
);
},
);
},
),
),
],
),
),
);
}
}
class _AddTodoInput extends StatefulWidget {
@override
State<_AddTodoInput> createState() => _AddTodoInputState();
}
class _AddTodoInputState extends State<_AddTodoInput> {
final TextEditingController _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
Expanded(
child: TextField(
controller: _controller,
decoration: const InputDecoration(
labelText: '添加新待办事项',
border: OutlineInputBorder(),
),
onSubmitted: (_) => _addTodo(),
),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: _addTodo,
child: const Text('添加'),
),
],
),
);
}
void _addTodo() {
// 通过 context.read 获取 TodoListNotifier 实例并调用 addTodo 方法
context.read<TodoListNotifier>().addTodo(_controller.text);
_controller.clear();
}
}
void main() {
runApp(const MaterialApp(home: TodoAppScreen()));
}
案例分析:
Todo 模型:定义了待办事项的数据结构,包含 id、title 和 isCompleted。copyWith 方法用于在修改 Todo 实例时保持不可变性,这是函数式编程和状态管理中的一个好实践。TodoListNotifier (ChangeNotifier):这个类继承自 ChangeNotifier,负责管理待办事项列表的实际状态 (_todos)。它提供了 addTodo、toggleTodoCompletion 和 removeTodo 等方法来修改列表。每次修改后,都会调用 notifyListeners() 来通知所有监听器(即 Consumer Widget)重建。ChangeNotifierProvider:在 TodoAppScreen 的 build 方法中,我们使用 ChangeNotifierProvider 来创建并提供 TodoListNotifier 的实例。这样,TodoAppScreen 的所有后代 Widget 都可以访问到这个 TodoListNotifier。Consumer<TodoListNotifier>:在 Expanded Widget 中,我们使用 Consumer 来监听 TodoListNotifier 的变化。当 TodoListNotifier 调用 notifyListeners() 时,Consumer 的 builder 方法会被调用,从而重建 ListView.builder,显示最新的待办事项列表。context.read<TodoListNotifier>().addTodo(_controller.text):在 _AddTodoInput 的 _addTodo 方法中,我们使用 context.read 来获取 TodoListNotifier 的实例,并调用 addTodo 方法。这里使用 read 而不是 watch 是因为我们只是想触发一个事件(添加待办事项),而不需要 _AddTodoInput 在 TodoListNotifier 状态变化时重建。List.unmodifiable(_todos):在 TodoListNotifier 的 todos getter 中,我们返回了一个不可修改的列表。这是一种防御性编程,可以防止外部直接修改 _todos 列表,从而确保所有状态修改都通过 TodoListNotifier 的方法进行,便于追踪和管理。这个案例展示了如何使用 Provider 模式来管理 Flutter 应用中的复杂状态。通过将业务逻辑封装在 ChangeNotifier 中,并使用 Provider 和 Consumer 来连接 UI 和状态,我们可以构建出清晰、可维护且响应迅速的 Flutter 应用。虽然这里没有直接使用 flutter_bloc 包,但 TodoListNotifier 的设计思想(封装业务逻辑、通过方法触发状态改变、通知 UI)与 BLoC 模式的核心原则是相通的。