你可能不会 Provider,来一起学一学吧!(分享一个不错的状态数据组织方式)
什么是 Provider?
Provider 是一个 Flutter 框架下的状态管理工具,不能说最好,但是是优秀状态管理工具中的佼佼者,再 pub.dev 上 Likes 也是所有状态管理工具的第一名,GitHub Star 也其高。括号:先对 Dart 或者 Flutter 包而言,因为 Dart packages 和 Flutter packages 的Star 数量普遍不高,毕竟入门门槛还是有点高。括号括转!
什么是状态管理?
Emmm,咋个解释呢,用 Vue 的直直到 Vuex 撒?用React 的直到 redux 撒?或者和 Mobx。就这种工具咯
先了解下数据
写客户端常与 APIs 打交道,如果做过后端的,其实就是将数据库的数据进行封装再输出到请求的 response body 里面。
那么,我们完全可以逆向思维,我们 APIs 返回的数据进行拆分重新组装成集合,是不是方便管理数据了呢?
为什么要逆向拆分还原会再服务器数据库的大致结构状态?
举个例子,一个帖子接口还会返回发帖人的用户信息。你存在一起作为帖子状态类成员。如果全局管理一个状态呢?比如你关注了这个用户,你不需要再去拉新的帖子数据和用户数据,直接修改这个用户再本地的关注状态即可。
服务端给的数据结构:
{
"title": "帖子",
"user": {
"name": "用户名"
}
}
拆分后:
{"title": "Post Title"}
{"name": "user name"}
当然,用 Json 去才分真的是难受。我个人再用 built_value 这个工具,先将数据 Model 化然后再拆分的
数据集合
如上数据,我们需要两个数据状态集合,一个叫 Users
一个叫 Posts
基类
为什么要写数据基类?我之前也不写,集合写多了后发现每个集合暴露的方法都不一样。开发和维护特别难受,边写基类的好好处就是将集合所暴露的 API 统一起来。
分享我边写的基类:
import 'package:flutter/foundation.dart';
/// 用户集合处理结果 keys 值枚举
///
/// 专门用于处理原始集合后返回处理结果
enum CollectionProviderActions {
/// 插入的数据集合
inserts,
/// 更新的数据集合
updates,
}
/// 数据集合基类,专门用于处理数据集合中通用部分
abstract class BaseCollectionProvider<K, V> with ChangeNotifier {
/// 存储数据集合原始数据信息
/// 以键值对形式存储私有的 [_collections] 中,类型为 `Map<K, V>` 形式存储
Map<K, V> _collections = {};
/// 只读形式获取 [_collections] 信息
Map<K, V> get collections => _collections;
/// 获取一个 [String] 类型的集合名称,主要用于 [watcher] 进行使用.
///
/// 使用的时候需要再子类中定义 [collectionName] 的 getter.
String get collectionName;
/// Returns true if this map contains the given [key].
///
/// Returns true if any of the keys in the map are equal to `key`
/// according to the equality used by the map.
bool containsKey(K key) => collections.containsKey(key);
/// Returns true if this map contains the given [value].
bool containsValue(V value) => collections.containsValue(value);
/// 获取当前对象的 `key` 集合
Iterable<K> get keys => collections.keys;
/// 获取当前对象的 `value` 集合
Iterable<V> get values => collections.values;
/// 原始好信息处理,处理完成后将返回以 `Map<CollectionProviderActions, Iterable<V>>`
/// 为基准的原始信息。
///
/// 其中 [CollectionProviderActions.inserts] 用于记录插入多少新文档;
/// 而 [CollectionProviderActions.updates] 用于记录更新了多少文档;
///
/// 返回信息如下:
/// ```dart
/// {
/// CollectionProviderActions.inserts: <V>[...],
/// CollectionProviderActions.updates: <V>[...],
/// }
/// ```
///
/// 注意,传入的参数 [elements] 可以是 `Iterable<V>` 和 `Iterable<Object>`
/// 或者 `Iterable<dynamic>`;程序判断出是 [V] 类型数据则直接跳过,其他情况均
/// 调用 [formObject] 函数进行数据处理。
Map<CollectionProviderActions, Iterable<V>> originInsertOrUpdate(
Iterable<Object> elements) {
/// 如果文档集合不存在,则直接返回空信息
if (elements == null || elements.isEmpty) {
return {
CollectionProviderActions.inserts: [],
CollectionProviderActions.updates: [],
};
}
/// 将 [elements] 转化为 `Iterable<V>` 数据。
Iterable<V> objects =
elements.where((element) => element is! V).map<V>(formObject).toList()
..addAll(elements.whereType<V>())
..removeWhere((element) => element == null);
/// 获取需要插入的数据集合
final Map<K, V> inserts = objects
.where((element) => !collections.containsKey(toCollectionId(element)))
.toList()
.asMap()
.map<K, V>(
(_, V element) => MapEntry(toCollectionId(element), element));
/// 获取需要更新的数据集合
final Map<K, V> updates = objects
.where((V element) => !inserts.containsKey(toCollectionId(element)))
.toList()
.asMap()
.map<K, V>(
(_, V element) => MapEntry(toCollectionId(element), element));
/// 将更新的数据集合放入 [collections] 中
_collections.updateAll((key, value) {
if (updates.containsKey(key)) {
return updates[key];
}
return value;
});
/// 将需要插入的数据插入到 [collections] 中。
_collections.addAll(inserts);
/// 返回处理皇后的元信息
return {
CollectionProviderActions.inserts: inserts.values,
CollectionProviderActions.updates: updates.values,
};
}
/// 插入或者更新指定的数据集合,其方法参考 [originInsertOrUpdate] 函
/// 数,其区别在于 [insertOrUpdate] 不返回任何结果信息,并且给插入的数
/// 据集合设置一个 [CloudBase] 的数据 [watcher] 来保证服务端数据更改。
Map<CollectionProviderActions, Iterable<V>> insertOrUpdate(
Iterable<Object> elements) {
final Map<CollectionProviderActions, Iterable<V>> state =
originInsertOrUpdate(elements);
notifyListeners();
Iterable<V> inserts = state[CollectionProviderActions.inserts];
if (inserts != null && inserts.isNotEmpty) {
watcher(inserts);
}
return state;
}
/// 转换 [element] 为数据监听者所需的 ID
String toDocId(V element);
/// 转换 [element] 为集合存储所需要的 [K] 值
K toCollectionId(V element);
@mustCallSuper
V formObject(Object value) {
if (value is V) {
return value;
}
return null;
}
/// 便捷从集合中使用 [key] 获取文档
V operator [](K key) => collections[key];
/// 用于便捷的设置单个文档的更新
void operator []=(K key, V value) => insertOrUpdate([value]);
}
集合子类
import 'package:app/models/user.dart';
import 'package:app/provider/collection.dart';
class UsersCollection extends BaseCollectionProvider<String, User> {
static UsersCollection _instance;
factory UsersCollection() {
if (_instance == null) {
_instance = UsersCollection._();
}
return _instance;
}
UsersCollection._();
@override
User formObject(Object value) {
return super.formObject(value) ?? User.fromJson(value);
}
@override
String toCollectionId(User value) => value.id;
@override
String toDocId(User value) => toCollectionId(value);
@override
String get collectionName => "users";
}
基本上常用的数据集合常用方法都有了,没有的可以直接获取 collections
这个 Map 进行操作
使用
我们在 MultiProvider
中加入集合 provider :
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => MomentsCollection()),
ChangeNotifierProvider(create: (_) => UsersCollection()),
],
child: child,
);
}
然后在 Widget 里面就可以使用 context.read
/context/watch
/context.select
方法进行使用咯。
仔细看子类
没错,子类增加了单例工厂函数!其原因很简单,我们总有一些地方没有 BuildContext
吧?增加单例就可以直接使用这个类进行数据更新。简直不要太方便。
没了
看啥呢?还不快起写代码?我就是很久没写文章了抽个十来分钟分享一些做了一年多 Flutter 开发的部分经验
GitHub
还在看?那去Follow一些我的GitHub吧! github.com/medz
本作品采用《CC 协议》,转载必须注明作者和本文链接
从服务器拿到数据后直接扔进集合,结果集中有数据可直接用,或者用的地方直接从状态集合中查询数据即可。