Flutter 数据存储 和状态管理的几种方式对比

flutter 项目中 数据的管理是个很重要的环节, 今天这篇博客主要就是讲讲 flutter 中数据的存储和状态管理的几种方式,以及优缺点.

数据存储

  • shared_preferences
  • sqflite
  • redux 和 redux_persist

下面说说各种存储方式的优缺点

shared_preferences

shared_preferences 其实就是个 key-value 的存储方式, 可以做一些开关配置的存储,比如是否首次登陆这种.

优点

存储简单,直接通过 putget key 值来存储和获取.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 如果_sp已存在,直接返回,为null时创建
static Future<SharedPreferences> get sp async {
if (_sp == null) {
_sp = await SharedPreferences.getInstance();
}
return _sp!;
}

static Future<bool> save(String key, String value) async {
Logger.i("save key = $key , value = $value");
return (await sp).setString(key, value);
}

static dynamic get(String key) async {
return (await sp).get(key);
}

缺点

一般都是一些配置的存储, 如果是对象或者数组的存储,就会很麻烦.

sqflite

数据库存储, 一般都是存储对象和数组.

优点

能够存储复杂的数据和数组

缺点

操作麻烦, 要先创建表,管理数据库的版本信息, 以及更新库表操作, 如果不熟练 很容易出错.

代码

dbHelp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var databasesPath = await getDatabasesPath();
String path = join(databasesPath, '$dbName.db');
//根据数据库文件路径和数据库版本号创建数据库表
db = await openDatabase(path, version: dbVersion, onConfigure: onConfigure,
onCreate: (Database db, int version) async {
var batch = db.batch();
GlobalConfigDao.createGlobalConfigV1(batch);
await batch.commit();
Timer(Duration(seconds: 1), () {
GlobalConfigDao.insertGlobalConfig(
GlobalConfigData(fontFamily: 0, themeColor: 1));
});
}, onUpgrade: (db, oldVersion, newVersion) async {
// var batch = db.batch();
// switch (oldVersion) {
// case 1:
// WelcomeConfigDao.createWelcomeConfigV2(batch);
// break;
// }
// await batch.commit();
});

GlobalConfigDao

1
2
3
4
5
6
7
static void createGlobalConfigV1(Batch batch) {
batch.execute('DROP TABLE IF EXISTS globalConfig');
batch.execute('''CREATE TABLE globalConfig (
id INTEGER PRIMARY KEY AUTOINCREMENT,
themeColor INTEGER,
fontFamily INTEGER)''');
}

redux redux_persist

redux_persist 其实说到底也类似于 shared_preferences, 他也是通过 key 来存储的, 只不过我们可以通过对象的 toJson 方法把对象转成 json,然后 redux_persist 把当前的 json 转成 string 存储到 key 里面去.

存储对象 readConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
part 'readConfig.g.dart';

@JsonSerializable(explicitToJson: true)
class ReadConfig {
int textFontIndex;

int bgColorIndex;

ReadConfig({this.bgColorIndex: 2, this.textFontIndex: 2});

factory ReadConfig.fromJson(Map<String, dynamic> json) =>
_$ReadConfigFromJson(json);

Map<String, dynamic> toJson() => _$ReadConfigToJson(this);

// 命名构造函数
ReadConfig.empty();
}

readConfigRedux

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

class UpdateReaderConfigAction {
ReadConfig readConfig;

UpdateReaderConfigAction(this.readConfig);
}

ReadConfig _update(ReadConfig readConfig, action) {
readConfig = action.readConfig;
return readConfig;
}

final ReadConfigReducer = combineReducers<ReadConfig>([
TypedReducer<ReadConfig, UpdateReaderConfigAction>(_update),
]);

state

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

class DzState {

ReadConfig readConfig;

DzState(
{
this.readConfig,});

static DzState fromJson(dynamic json) => json != null
? DzState(
readConfig: ReadConfig.fromJson(json["readConfig"] ??
ReadConfig(bgColorIndex: 2, textFontIndex: 2).toJson()))
: DzState.empty();

dynamic toJson() => {
"readConfig": readConfig,
};

static DzState empty() {
return DzState(
readConfig: ReadConfig(bgColorIndex: 2, textFontIndex: 2),
);
}
}

DzState appReducer(DzState state, action) {
return new DzState(
readConfig: ReadConfigReducer(state.readConfig, action));
}

在 main.dart 中调用

1
2
3
4
5
6
7
8
9
10
11
12
13
final persistor = Persistor<DzState>(
storage: FlutterStorage(),
serializer: JsonSerializer<DzState>(DzState.fromJson),
);

// Load initial state
final initialState = await persistor.load();

final store = Store<DzState>(
appReducer,
initialState: initialState ?? DzState.empty(),
middleware: [persistor.createMiddleware()],
);

优点

既可以存简单的 key-value 的值, 也可以存储对象和数组.

缺点

创建一个对象比较复杂, 很容易初始化出错, 而且没法给对象中的单独变量修改,要修改的时候 必须把当前对象获取到,然后修改变量的值, 然后把整个对象给修改掉.

1
2
3
4
5

ReadConfig config = DzStore.getStore().state.readConfig;

config.textFontIndex = value.toInt();
DzStore.getStore().dispatch(UpdateReaderConfigAction(config));

数据状态管理

  • redux
  • provider
  • bloc

数据的状态管理 一般用这三个.下面分别说说三个的优缺点.

redux

优点

只要 redux 里面的数值改变, 引用的地方都会刷新,比如启动多个页面 其中一个页面头像和昵称修改,那么回退回去后,页面的头像和昵称也会发生修改.数据及时同步

缺点

只要用 StoreBuilder<DzState>(builder: (storeContext, store) 的地方, 都会重新刷新,很容易造成没必要的性能浪费.

provider

优点

只要 provider 里面的数据发生改变, 引用当前的 provider 的页面才会重新刷新

缺点

如果当前 provider 有多个状态, 那么只要修改其中一个状态, 那页面也是都会刷新.也容易造成性能的浪费.

bloc

bloc 可以说是集成了 redux 和 provider 的所有优点, 并且优化了他们的缺点.

bloc 定义事件, 每个事件一个 state, 只要处理返回的对象 state 就行了,就减少了性能的浪费, 减少了没必要的刷新.

后面就在实战项目中来体现.

Adhere to original technology sharing, your support will encourage me to continue to create!