Flutter开发者

Flutter中的本地存储

2018-04-13

上期回顾

在上一篇文章中我们学习了在Dart中的异步操作,你以为我没事啊还特地给你们专门写一篇文章啊,当然是有用的啊。

想必大家都知道所有的文件操作都是耗时的,那么肯定都是要在异步下进行的,不然的话那就真的要让用户死等啊,所以异步操作在文件的存储过程中显得异常的重要。

好吧,还是回归今天的主题,我们还是来看下Flutter中的本地存储吧

Flutter本地存储

和Android、Ios类似,Flutter也支持Preferences(Shared Preferences and NSUserDefaults) 、文件、和Sqlite3。

只不过要想使用这个功能需要引入官方仓库的相应插件,那么我们就分别来看下这三种存储方式的使用方法。

Preferences存储

Flutter中本身并不支持Preferences存储,需要借助于第三发的组件来实现。

打开 https://github.com/flutter/plugins


或者 https://pub.dartlang.org/flutter

可以在上面找到需要官方和第三方提供的其他组件,我们使用的shared_preferences就是其中的一个
打开shared_preferences插件对象的页面即可看到插件相关的信息

现在我们需要在项目里面引入shared_preferences插件

第三发插件的引用

  1. 打开项目的pubspec.yaml配置我文件在dependencies:节点下新增如下配置 > shared_preferences: “^0.4.1”

  2. 点击开发工具提示的packages get按钮或者在命令行输入flutter packages get来同步第三方插件

  3. 在自己的Dart文件中引入插件即可正常使用了
    import ‘package:shared_preferences/shared_preferences.dart’;

下面还是举个例子来说明下

实现输入一段字符串进行保存和获取操作

还是直接来看代码:

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
67
68
69
70
71
72
73
74
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
runApp(new MaterialApp(home: new MyApp()));
}



class MyApp extends StatelessWidget {
final String mUserName = "userName";
final _userNameController = new TextEditingController();

@override
Widget build(BuildContext context) {
save() async{
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString(mUserName, _userNameController.value.text.toString());
}

Future<String> get() async {
var userName;

SharedPreferences prefs = await SharedPreferences.getInstance();
userName = prefs.getString(mUserName);
return userName;
}

return new Builder(builder: (BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text("SharedPreferences"),
),
body: Center(
child: new Builder(builder: (BuildContext context){
return
Column(
children: <Widget>[
TextField(
controller: _userNameController,
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(top: 10.0),
icon: Icon(Icons.perm_identity),
labelText: "请输入用户名",
helperText: "注册时填写的名字"),
),
RaisedButton(
color: Colors.blueAccent,
child: Text("存储"),
onPressed: () {
save();
Scaffold.of(context).showSnackBar(
new SnackBar(content: Text("数据存储成功")));
}),
RaisedButton(
color: Colors.greenAccent,
child: Text("获取"),
onPressed: () {
Future<String> userName = get();
userName.then((String userName) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text("数据获取成功:$userName")));
});
}),
],
);
}),
),
);
});
}
}

首先我们创建了一个TextField用来获取用户输入,然后我们再下面定义看了连个按钮,每当当即存储按钮都会触发save() 方法,每当点击获取按钮都会触发get()方法。

接下来我们还是来看下这两个方法里面做了什么吧。

1
2
3
4
save() async{
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString(mUserName, _userNameController.value.text.toString());
}

在上面save方法中我们可以看到我们给它加上了async和await关键字,因为SharedPreferences的存贮也是一个轻量级的耗时操作,所以我们也是需要在异步中进行的。

我们使用SharedPreferences.getInstance()方法来实例化SharedPreferences对象,使用它的setString方法来存储用户输入的字符串。

setString(key, value)

其中key就是你存贮的名称,value就是你存储的值

当然,SharedPreferences里有很多存储其他对象的方法比如:

prefs.setBool(key, value)

prefs.setDouble(key, value)

prefs.setInt(key, value)

prefs.setStringList(key, value)

接下来来看下get方法

1
2
3
4
5
6
7

Future<String> get() async {
var userName;
SharedPreferences prefs = await SharedPreferences.getInstance();
userName = await prefs.getString(mUserName);
return userName;
}

在get方法中我们同样实例化了一个SharedPreferences对象,并且调用SharedPreferences的getString方法来获取我们存入的对象。

getString(key)

key就是我们刚才存入的值,我们通过这个值可以在本地查找到我们存入的对象并返回。

同样的,get方法也是耗时操作,同样需要异步执行,我们使用async和await来使得get方法异步并返回了一个泛型为String的Future对象。

1
2
3
4
5
Future<String> userName = get();
userName.then((String userName) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text("数据获取成功:$userName")));
});

我们使用获得的Future对象调用then()方法,当get方法执行完后就会自动触发then()方法里面的操作弹出showSnackBar。

好吧,咱们还是去android目录下看下这个SharedPreferences文件在不在吧

嗯,其实也不难吧,接下来,来看下文件操作吧。

文件存储

和SharedPreferences操作一样,Flutter内部并没有提供对本地文件的支持,但是官方给我们提供了第三方的支持库哦。

同样的方法,我们需要在pubspec.yaml文件中引入

path_provider: ^0.4.0

然后调用flutter packages get

最后在自己的Dart文件中引入

import ‘package:path_provider/path_provider.dart’;

即可使用Flutter中的文件存储

在path_provider中有三个获取文件路径的方法:

  • getTemporaryDirectory()//获取应用缓存目录,等同IOS的NSTemporaryDirectory()和Android的getCacheDir() 方法
  • getApplicationDocumentsDirectory()获取应用文件目录类似于Ios的NSDocumentDirectory和Android上的 AppData目录
  • getExternalStorageDirectory()//这个是存储卡,仅仅在Android平台可以使用

下面我们就以把文件存在应用文件目录举个例子

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';

void main() {
runApp(new MaterialApp(home: new MyApp()));
}

class MyApp extends StatelessWidget {
final _userNameController = new TextEditingController();
final mFile=null;

Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();

return directory.path;
}

Future<File> get _localFile async {
final path = await _localPath;
return new File('$path/nameFile.txt');
}

Future<File> save(String name) async {
final file = await _localFile;
return file.writeAsString(name);
}

@override
Widget build(BuildContext context) {
Future<String> get() async {
final file = await _localFile;
return file.readAsString();
}

return new Builder(builder: (BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("FileOperator"),
),
body: new Center(
child: new Builder(builder: (BuildContext context) {
return new Column(
children: <Widget>[
new TextField(
controller: _userNameController,
decoration: new InputDecoration(
contentPadding: const EdgeInsets.only(top: 10.0),
icon: new Icon(Icons.perm_identity),
labelText: "请输入用户名",
helperText: "注册时填写的名字"),
),
RaisedButton(
color: Colors.blueAccent,
child: Text("存储"),
onPressed: () {
save(_userNameController.text.toString());
Scaffold.of(context).showSnackBar(
new SnackBar(content: new Text("数据存储成功")));
}),
RaisedButton(
color: Colors.greenAccent,
child: Text("获取"),
onPressed: () {
Future<String> userName = get();
userName.then((String userName) {
Scaffold.of(context).showSnackBar(
new SnackBar(content: Text("数据获取成功:$userName")));
});
}),
],
);
}),
),
);
});
}
}

好吧,原谅我比较懒,界面还是上面的界面,只不过我们把上面的save和get方法变了下。

首先我们先获取存储目录

1
2
3
4
5
6

Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();

return directory.path;
}

然后在本地建立文件(不存在这个名字的自动创建并返回,存在则直接返回这个文件对象),名字就叫做
nameFile吧。

1
2
3
4
5

Future<File> get _localFile async {
final path = await _localPath;
return new File('$path/nameFile.txt');
}

然后就是存储输入框内的内容了

1
2
3
4
5

Future<File> save(String name) async {
final file = await _localFile;
return file.writeAsString(name);
}

我们使用上面获取到的文件直接直接调用writeAsString即可,当然它会把这个文件对象返回给你,你可以存储下这个文件对象在下次使用

最后,我们来读取本地的文件

1
2
3
4
5

Future<String> get() async {
final file = await _localFile;
return file.readAsString();
}

我们直接调用file的readString方法来获取字符串并返回泛型为String的Future对象

1
2
3
4
5

Future<String> userName = get();
userName.then((String userName) {
Scaffold.of(context).showSnackBar(
new SnackBar(content: Text("数据获取成功:$userName")));

然后我们还是使用上面的代码通过Future的then方法来回去反悔的数据并showSnackBar

好吧,我们还是去看下,这个名字为nameFile.txt文件是否存在。

果不其然,它静静的躺在那里,哈。

最后,我们来看下Flutter中Sqlite的用法

Sqlite

和SharedPreferences和文件操作操作一样,Flutter内部并没有提供对sqlite的支持,但是官方给我们提供了第三方的支持库哦。

同样的方法,我们需要在pubspec.yaml文件中引入

sqflite: >=0.8.5

然后调用flutter packages get

最后在自己的Dart文件中引入

import ‘package:sqflite/sqflite.dart’;

即可在Flutter使用Sqlite

同样的,我慢还是先贴代码:

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';

void main() {
runApp(new MaterialApp(home: new MyApp()));
}

class MyApp extends StatelessWidget {
final _userNameController = new TextEditingController();


Future<String> get _dbPath async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "name.db");

return path;
}

Future<Database> get _localFile async {
final path = await _dbPath;

Database database = await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {

await db.execute(
"CREATE TABLE user (id INTEGER PRIMARY KEY, name TEXT)");
});
return database;
}

Future<int> save(String name) async {
final db = await _localFile;
return db.transaction((trx){
trx.rawInsert( 'INSERT INTO user(name) VALUES("$name")');
});
}
Future<List<Map>> get() async {
final db = await _localFile;
List<Map> list= await db.rawQuery('SELECT * FROM user');
return list;
}
@override
Widget build(BuildContext context) {


return new Builder(builder: (BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("FileOperator"),
),
body: new Center(
child: new Builder(builder: (BuildContext context) {
return new Column(
children: <Widget>[
new TextField(
controller: _userNameController,
decoration: new InputDecoration(
contentPadding: const EdgeInsets.only(top: 10.0),
icon: new Icon(Icons.perm_identity),
labelText: "请输入用户名",
helperText: "注册时填写的名字"),
),
RaisedButton(
color: Colors.blueAccent,
child: Text("存储"),
onPressed: () {
save(_userNameController.text.toString());
Scaffold.of(context).showSnackBar(
new SnackBar(content: new Text("数据存储成功")));
}),
RaisedButton(
color: Colors.greenAccent,
child: Text("获取"),
onPressed: () {
Future<List<Map>> userName = get();
userName.then((List<Map> userNames) {
Scaffold.of(context).showSnackBar(
new SnackBar(content: Text("数据获取成功:$userNames")));
});
}),
],
);
}),
),
);
});
}
}

首先,我们需要获取一下我们数据库存储的目录,数据库名字为name.db

1
2
3
4
5
6
Future<String> get _dbPath async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "name.db");

return path;
}

然后建立库和数据表,并返回泛型为Database的Future对象(我们这里是使用的失去了语句建立的数据表操作,大家可以根据自己需要定制相应的ORM映射库)

我们建立了一个表名为user 主键为id,一个Text类型name的数据表。

1
2
3
4
5
6
7
8
9
10
11
Future<Database> get _localFile async {
final path = await _dbPath;

Database database = await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {

await db.execute(
"CREATE TABLE user (id INTEGER PRIMARY KEY, name TEXT)");
});
return database;
}

接着,我们拿着这个Database对象就可以存储数据了

这里我们在事务里执行sql语句

1
2
3
4
5
6
7

Future<int> save(String name) async {
final db = await _localFile;
return db.transaction((trx){
trx.rawInsert( 'INSERT INTO user(name) VALUES("$name")');
});
}

然后,我们点击获取按钮,获取数据

1
2
3
4
5
Future<List<Map>> get() async {
final db = await _localFile;
List<Map> list= await db.rawQuery('SELECT * FROM user');
return list;
}

这里我们的查询操作直接返回了一个List,因为我们获取出来的数据不一定是一条啊。

最后,我们在点击事件触发获取到相应值并处理

1
2
3
4
Future<List<Map>> userName = get();
userName.then((List<Map> userNames) {
Scaffold.of(context).showSnackBar(
new SnackBar(content: Text("数据获取成功:$userNames")));

好吧,下面来看下效果

我们在来看下应用目录下有没有这个数据库文件吧

可以看到数据库文件已经在应用目录下了,数据库文件我就不拿出来看了哈

其实,今天提到的本地存储操作都是比较简单,大家可以在下面多多试一试相应的操作,因为在以后的应用开发过程中会用的很多的。^笔芯^

小结

  • 可以在https://pub.dartlang.org/flutter获取第三发插件
  • 在pubspec.yaml引入或者更新第三发插件
  • SharedPreferences、文件、数据库操作都是命耗时操作,需要异步执行

试一试

今天的我们对SharedPreferences、文件、数据库获取的Future对象都是通过async和await获得的,那么大家在下面尝试把今天获取Future的方式改为 Future api方式去获取并完成今天的例子。

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

打赏请备注姓名或者昵称,方便我后期统计哦

关注公众号,及时查阅最新文章