0%

flutter数据共享系列——随记

flutter数据共享系列——随记

Provider

InheritedWidget 解决了数据共享问题。迎面也带来数据刷新导致的组件不必要更新问题。Provider基于InheritedWidget实现数据共享,数据更新,定向通知组件更新等。

接下来我们先从Provider使用开始切入,逐步分析Provider的实现,以及对组件的应用进行熟悉。

就拿官方文档开始:

新建一个模型Counter

1
2
3
4
5
6
7
8
9
10
class Counter with ChangeNotifier {
int _count = 0;

int get count => _count;

void increment() {
_count++;
notifyListeners();
}
}

在合适的位置初始化

这里我们选择main方法:

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

使用并修改数据

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
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}

class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Example'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Text('You have pushed the button this many times:'),

Extracted as a separate widget for performance optimization.
As a separate widget, it will rebuild independently from [MyHomePage].

This is totally optional (and rarely needed).
Similarly, we could also use [Consumer] or [Selector].
Count(),
],
),
),
floatingActionButton: FloatingActionButton(
key: const Key('increment_floatingActionButton'),

Calls `context.read` instead of `context.watch` so that it does not rebuild
when [Counter] changes.
onPressed: () => context.read<Counter>().increment(),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}

class Count extends StatelessWidget {
const Count({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Text(

Calls `context.watch` to make [Count] rebuild when [Counter] changes.
'${context.watch<Counter>().count}',
key: const Key('counterState'),
style: Theme.of(context).textTheme.headline4);
}
}

使用注意事项

1. 共享数据定义为私有属性,提供get方法和update方法

这样可以有效的保护数据结构,统一修改入口和获取方法。

2. 适当隔离会进行rebuild的组件,常用的方式有三种:

  • 单独封装组件
  • 通过Consumer包裹
  • 通过Selector包裹,selector可以在某些值不变的情况下,防止rebuild。常用的地方是针对列表中个别数据进行修改。

3. 区分watchread的使用

watchread是Provider框架内部对BuildContext的扩展类。用户获取父级组件指定数据入口。区别在于是否添加了linsten,这个关系到是否需要实时刷新。
简单区分两种场景:

  • watch:界面监听数据,更新页面
  • read:响应业务交互,去操作更新数据

两种方法的源码也很简单,只是为了方便生成的拓展类:

1
2
3
4
5
6
7
8
9
10
11
12
13
 Exposes the [read] method.
extension ReadContext on BuildContext {
T read<T>() {
return Provider.of<T>(this, listen: false);
}
}

Exposes the [watch] method.
extension WatchContext on BuildContext {
T watch<T>() {
return Provider.of<T>(this);
}
}

这里我们来看看Provider.of源码:

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
  static T of<T>(BuildContext context, {bool listen = true}) {
// 移出部分不必要代码

final inheritedElement = _inheritedElementOf<T>(context);

if (listen) {
context.dependOnInheritedElement(inheritedElement);
}
return inheritedElement.value;
}

static _InheritedProviderScopeElement<T> _inheritedElementOf<T>(
BuildContext context,
) {
// 移出部分不必要代码
_InheritedProviderScopeElement<T>? inheritedElement;

if (context.widget is _InheritedProviderScope<T>) {
context.visitAncestorElements((parent) {
inheritedElement = parent.getElementForInheritedWidgetOfExactType<
_InheritedProviderScope<T>>() as _InheritedProviderScopeElement<T>?;
return false;
});
} else {
inheritedElement = context.getElementForInheritedWidgetOfExactType<
_InheritedProviderScope<T>>() as _InheritedProviderScopeElement<T>?;
}

if (inheritedElement == null) {
throw ProviderNotFoundException(T, context.widget.runtimeType);
}

return inheritedElement!;
}

原来加不加listen的区别在于获取数据的方式是getElementForInheritedWidgetOfExactTypeordependOnInheritedElementdependOnInheritedElement会新增一个注册,这个注册会调用在数据变更后,调用消费者的didChangeDependencies 。稍微具体点分析可以查看我之前的文章——
记InheritedWidget使用思考

ChangeNotifier

实现Listenable接口的一个简单类,官方给的说明很简单:

A class that can be extended or mixed in that provides a change notification

可以扩展或混合的类,提供更改通知

实现了算法复杂度为O(1)去添加监听,O(N)去移除监听。对数据更新高效通知页面去刷新。provider的数据模型均得继承与它。

ChangeNotifierProvider

有了数据模型,接下来就开始创建我们的ChangeNotifier,就要用到ChangeNotifierProvider

先说一个错误的示例,错误的示例,错误的示例,在build中通过ChangeNotifierProvider.value去创建:

1
2
3
4
ChangeNotifierProvider.value(
value: new MyChangeNotifier(),
child: ...
)

这样会造成内存泄露和潜在的bug——参考

当然,这个方法存在肯定是有他的意义的——如果你已经有了ChangeNotifier实例,就可以通过ChangeNotifierProvider.value进行构造。而不是选用create

正确的方法是通过creat方法去构建:

1
2
3
4
ChangeNotifierProvider(
create: (_) => new MyChangeNotifier(),
child: ...
)

不要传入变量去构建ChangeNotifier,这样的话,当变量更新,ChangeNotifier是不会去更新的。

1
2
3
4
5
6
int count;

ChangeNotifierProvider(
create: (_) => new MyChangeNotifier(count),
child: ...
)

如果你确实需要传入变量,请使用ChangeNotifierProxyProvider。具体ChangeNotifierProxyProvider使用,这里就不探讨了。