一. 简介

在 Flutter 中,Provider 是一个非常流行且官方推荐的状态管理方案。它通过依赖注入(Dependency Injection)和响应式机制,让 UI 与数据状态解耦,实现“数据改变 → UI 自动刷新”的逻辑。

  • 基于 观察者模式 实现。
  • 它通过 ChangeNotifier 让数据变化自动通知订阅者,解耦 UI 和数据逻辑。

适用场景

  • 单页面状态管理
  • 跨组件/跨路由状态共享
  • 简单全局状态(主题、语言、计数器等)

核心类介绍

  • ChangeNotifier:一个实现了观察者模式的类,当状态发生变化时,调用’notifyListeners()’方法通知所有监听者更新
  • ChangeNotifierProvider:一个 Widget,用于在 Widget 树中提供’ChangeNotifier’实例,使其子树中的 Widget 可以访问该实例
  • Consumer:用于在子 Widget 中获取并监听’ChangeNotifier’实例,当状态变化时会重建自身
  • Provider.of:另一种获取’ChangeNotifier’实例的方法,可以选择是否监听状态变化

二. 基本使用流程

使用 Provider 管理状态通常遵循以下步骤:

  1. 创建一个继承自 ChangeNotifier 的状态类,封装需要共享的状态和修改状态的方法
  2. 使用 ChangeNotifierProvider 在 Widget 树的适当位置提供该状态实例
  3. 在需要使用状态的子 Widget 中,通过 ConsumerProvider.of 获取状态并使用

下面通过一个简单的计数器示例来演示基本用法:

1. 创建状态类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import 'package:flutter/foundation.dart';

import 'package:flutter/foundation.dart';

// 继承 ChangeNotifier
class Counter extends ChangeNotifier {
int _count = 0;

// 提供获取状态的方法
int get count => _count;

// 提供修改状态的方法
void increment() {
_count++;
// 通知所有监听者
notifyListeners();
}

void decrement() {
_count--;
notifyListeners();
}

void reset() {
_count = 0;
notifyListeners();
}
}

2. 提供状态

在 Widget 树中使用 ChangeNotifierProvider 提供状态,通常放在应用的根部:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import 'package:provider/provider.dart';

void main() {
runApp(
// 提供 Counter 实例
ChangeNotifierProvider(
create: (context) => Counter(),
child: const MyApp(),
),
);
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Provider Demo',
home: const CounterPage(),
);
}
}

3. 消费状态

在子 Widget 中通过 ConsumerProvider.of 获取并使用状态:

使用 Consumer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import 'package:provider/provider.dart';

class CounterPage extends StatelessWidget {
const CounterPage({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Counter Demo')),
body: Center(
// 使用 Consumer 获取 Counter 实例
child: Consumer<Counter>(
builder: (context, counter, child) {
// builder 方法会在状态变化时重建
return Text(
'Current count: ${counter.count}',
style: const TextStyle(fontSize: 24),
);
},
),
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () {
// 使用 Provider.of 获取实例(不监听变化)
Provider.of<Counter>(context, listen: false).decrement();
},
child: const Icon(Icons.remove),
),
const SizedBox(width: 10),
FloatingActionButton(
onPressed: () {
Provider.of<Counter>(context, listen: false).increment();
},
child: const Icon(Icons.add),
),
],
),
);
}
}

Consumer 性能优化

Consumerchild 参数可以用于优化性能,避免不必要的重建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Consumer<Counter>(
builder: (context, counter, child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 这个 Text 会在状态变化时重建
Text('Count: ${counter.count}'),
// 这个子组件不会重建,因为它被提取到了 child 参数中
child!,
],
);
},
// 这个子组件只会构建一次
child: const Text('This is a static text'),
)

使用 Provider.of

Provider.of<T>(context) 会获取最近的 T 类型的 Provider,并在状态变化时重建当前 Widget:

1
2
3
4
5
// 监听状态变化,状态变化时会重建当前 Widget
final counter = Provider.of<Counter>(context);

// 不监听状态变化,通常用于修改状态的场景
final counter = Provider.of<Counter>(context, listen: false);

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class CounterDisplay extends StatelessWidget {
const CounterDisplay({super.key});

@override
Widget build(BuildContext context) {
// 获取 Counter 实例并监听变化
final counter = Provider.of<Counter>(context);

return Text(
'Count: ${counter.count}',
style: const TextStyle(fontSize: 24),
);
}
}

class CounterActions extends StatelessWidget {
const CounterActions({super.key});

@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: () {
// 获取 Counter 实例但不监听变化
Provider.of<Counter>(context, listen: false).increment();
},
child: const Icon(Icons.add),
);
}
}

4. 多个状态管理

当应用中有多个独立的状态需要管理时,可以使用 MultiProvider 来组织多个 Provider:

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => Counter()),
ChangeNotifierProvider(create: (context) => ThemeProvider()),
ChangeNotifierProvider(create: (context) => UserProvider()),
],
child: const MyApp(),
),
);
}

这样,在子组件中可以分别获取不同的状态:

1
2
3
4
5
// 获取计数器状态
final counter = Provider.of<Counter>(context);

// 获取主题状态
final themeProvider = Provider.of<ThemeProvider>(context);

三. 实现计数器状态管理

pubspec.yaml 中添加依赖

1
2
3
4
dependencies:
flutter:
sdk: flutter
provider: ^6.0.5
  • ^6.0.5 表示使用 ‘6.0.5 及以上的小版本更新‘。
  • 根据项目需求,也可以用最新版本,如 provider: ^6.1.0

获取依赖

在终端执行:

1
flutter pub get
  • 这会下载 provider 包并更新项目依赖。

创建状态类(Model)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';

//继承ChangeNotifier后,可以通知所有订阅者
class CounterModel with ChangeNotifier {
int _count;//要保存的数据,我这里实现计数器,所以只有一个int变量
CounterModel(this._count);

void add() {//提供全局方法,让全局计数+1
_count++;
notifyListeners(); //当数值改变后,通知所有订阅者刷新ui
}

get count => _count; //提供全局get方法获取计数总值
}

注入全局状态main.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_demo/provider/Counter.dart';
import 'package:provider_demo/widgets/menu.dart';

void main() {
runApp(
ChangeNotifierProvider(//全局状态设置
create: (context) => CounterModel(0),//创建一个countermodel全局状态类,管理count值
child: MyApp(),
),
);
}
  • ChangeNotifierProvider 将状态注入整个 widget 树。
  • 所有子 widget 都可以通过 Provider.of、context.watch 或 context.read 获取数据。

计数器主页面实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Provider'),
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;

@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: [
IconButton(
icon: Icon(Icons.golf_course),
onPressed: () {//创建一个跳转界面,跳转到新的路由,本跳转不传任何值
Navigator.push(context, MaterialPageRoute(builder: (context) {
return MenuView();
}));
},
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
//以下代码Provider.of<model类名>(context).属性值
//请注意,属性值在model类中必须有get方法
"${Provider.of<CounterModel>(context).count}",
style: Theme.of(context).textTheme.headline4,//字体样式
),
],
),
),
floatingActionButton: FloatingActionButton(//创建一个悬浮按钮
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}

void _incrementCounter() {//悬浮按钮点击事件
//context.read<model类名>().model中的方法;
context.read<CounterModel>().add();
}
}

一个简单的全局共享就完成了

注:不建议在程序入口初始化Provider,这里只是为了演示方便这么做,实际项目中要是都在程序入口初始化可能会导致内存急剧增加,除非是共享一些全局的状态,例如app日夜间模式切换,中英文切换等。

只要你在 runApp 或顶层 Widget 注入了 ChangeNotifierProvider,这个状态就存在 整个 widget 树中。 所有子页面都可以直接访问同一个状态实例,无需通过构造函数传值或返回值传递。那么怎么管理多个状态呢?
很简单,在main.dart中将:

1
2
3
4
5
6
7
8
void main() {
runApp(
ChangeNotifierProvider(//全局状态设置
create: (context) => CounterModel(0),//创建一个countermodel全局状态类,管理count值
child: MyApp(),
),
);
}

改为

1
2
3
4
5
6
7
8
9
10
11
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => CounterModel(0)),
//可以继续添加,语法如上,这样可以全局管理多个状态
],
child: MyApp(),
),
);
}