跳转到正文
YGJoon 技术博客

Flutter Provider 状态管理详解

发布于:
·1.0k 字 · 约 3 分钟·作者:
官方推荐的 InheritedWidget 封装:ChangeNotifierProvider 供数据,Consumer、watch、read 三种取数姿势
Flutter Provider 状态管理详解文章封面
目录

简介

Provider 是 Flutter 官方推荐的状态管理方案,它基于 InheritedWidget 封装,提供了一种简单且可扩展的状态管理方式。

基本概念

ChangeNotifier

class Counter extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners(); // 通知监听器数据已更新
  }
}

Provider 类型

// 基础 Provider
Provider<T>

// 可监听变化的 Provider
ChangeNotifierProvider<T>

// 多个 Provider 组合
MultiProvider

// 消费者组件
Consumer<T>

基本用法

创建 Provider

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => Counter(),
      child: MyApp(),
    ),
  );
}

访问数据

// 方式1: Consumer
Consumer<Counter>(
  builder: (context, counter, child) {
    return Text('Count: ${counter.count}');
  },
);

// 方式2: Provider.of
final counter = Provider.of<Counter>(context);
Text('Count: ${counter.count}');

// 方式3: context.read/watch
Text('Count: ${context.watch<Counter>().count}');

完整示例

// 数据模型
class TodoModel extends ChangeNotifier {
  final List<Todo> _todos = [];
  
  List<Todo> get todos => _todos;
  
  void addTodo(Todo todo) {
    _todos.add(todo);
    notifyListeners();
  }
  
  void toggleTodo(int index) {
    _todos[index].completed = !_todos[index].completed;
    notifyListeners();
  }
  
  void removeTodo(int index) {
    _todos.removeAt(index);
    notifyListeners();
  }
}

// 页面组件
class TodoListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Todo List')),
      body: Consumer<TodoModel>(
        builder: (context, todoModel, child) {
          return ListView.builder(
            itemCount: todoModel.todos.length,
            itemBuilder: (context, index) {
              final todo = todoModel.todos[index];
              return ListTile(
                leading: Checkbox(
                  value: todo.completed,
                  onChanged: (_) => todoModel.toggleTodo(index),
                ),
                title: Text(todo.title),
                trailing: IconButton(
                  icon: Icon(Icons.delete),
                  onPressed: () => todoModel.removeTodo(index),
                ),
              );
            },
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 添加新任务
          context.read<TodoModel>().addTodo(
            Todo(title: 'New Task ${DateTime.now()}'),
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

// 主应用
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => TodoModel()),
        ChangeNotifierProvider(create: (_) => ThemeModel()),
      ],
      child: Consumer<ThemeModel>(
        builder: (context, themeModel, child) {
          return MaterialApp(
            theme: themeModel.currentTheme,
            home: TodoListPage(),
          );
        },
      ),
    );
  }
}

高级用法

选择性重建

// 使用 Selector 只在特定值变化时重建
Selector<TodoModel, int>(
  selector: (context, todoModel) => todoModel.todos.length,
  builder: (context, todoCount, child) {
    return Text('Todo count: $todoCount');
  },
);

Provider 组合

MultiProvider(
  providers: [
    ChangeNotifierProvider(create: (_) => UserModel()),
    ProxyProvider<UserModel, CartModel>(
      update: (context, userModel, previous) =>
          CartModel(userModel.userId),
    ),
    ChangeNotifierProxyProvider<UserModel, SettingsModel>(
      create: (_) => SettingsModel(),
      update: (context, userModel, settings) =>
          settings!..updateUser(userModel),
    ),
  ],
  child: MyApp(),
)

最佳实践

  1. 监听粒度用 Consumer/Selector:整页 context.watch 一个 ChangeNotifier,任何字段一变整页 rebuild;只关心 user.name 就用 Selector<UserModel, String>(selector: (_, u) => u.name),name 不变就不 rebuild
  2. 回调里用 context.read,build 里用 context.watch:按钮 onPressed 里 context.read<Cart>().add(item);read 在 build 里拿不到更新,watch 在回调里用会抛异常——initState 里只能 read,不能 watch
  3. 列表 item 的 provider 用 .value 构造:ListView.builder 里写 ChangeNotifierProvider.value(value: items[i]);用 create 会在 item 复用时错乱、重复 dispose
  4. ChangeNotifier 持有的 StreamSubscription/Timer,在 dispose() 里取消:页面 pop 了回调还在跑,notifyListeners 触发已 dispose 的监听直接抛异常

注意事项

  1. 绝不在 build 方法里调 notifyListeners:build → notify → 标 dirty → 又 build,死循环,页面直接卡死——这是 Provider 新手第一坑
  2. ChangeNotifier 别塞太多字段:一个 model 管 20 个字段,改一个字段全量通知所有监听者;按”一起变”拆 model(UserModel、CartModel 分开)
  3. 只有跨页面共享的状态才放顶层 Provider:页面内部状态用 StatefulWidget 的 setState 就够了,别为了”统一”全上 Provider
  4. 异步操作里 notify 前先确认还活着:await 之后页面可能已经 pop 了,调 notify 前检查 hasListeners,或者包 try-catch

实战案例

Provider 我早期用过,后来全切到 GetX。切换的直接导火索是 context 依赖:Provider 的 context.read/watch 必须在 widget 树里有 context,但在 controller/service 层(比如网络回调里要更新全局用户状态)拿不到 context,得层层传,非常别扭。GetX 的 Get.find<UserController>() 哪都能调。当然 Provider 也有优点:它是官方推荐,心智模型简单,和 ChangeNotifier 配合学习成本低。如果是给新手团队或 Demo 项目,我反而推荐 Provider——选 GetX 是因为我熟,不是因为它”更好”。状态管理选型,团队熟悉度权重应该占一半。

总结

Provider 提供了一种简单且强大的状态管理方案,通过合理使用可以有效管理应用状态。理解并掌握 Provider 的使用对于开发高质量的 Flutter 应用至关重要。


分享这篇文章:


相关文章

上一篇
Flutter 嵌套路由详解
下一篇
Flutter GetX 状态管理详解