今天我们来介绍几个 Persistence 的方法,即是用来储存数据,将数据存在我们的手机等硬体里,以便我们在重开App 或重新开机後也能够使用之前保存的数据的方法
相比於其他储存资料於本地的方法,SQLite 资料库能够提供更为迅速的插入、更新、查询功能
之後范例会用到一些基本的SQL语句,如果你对於SQLite和SQL的各种语句还不熟悉,请查看SQLite官方的教程 SQLite教程
首先添加依赖:
...
dependencies:
sqflite: ^1.3.1
path: ^1.7.0
...
范例:
新建一个要储存的Model 类,之後存在资料库里的资料就会有这些栏位
class Dog {
final int id;
final String name;
final int age;
Dog({this.id, this.name, this.age});
}
在你准备读写资料库的数据之前,你要先打开这个资料库,此时需要以下两个步骤才可以打开资料库:
sqflite
package里的getDatabasesPath
方法并配合path
package里的 join
方法定义资料库的路径sqflite
中的openDatabase()
功能打开资料库为了使用关键字
await
,必须将代码放在async
函数内。您应该将以下所有表函数放在内void main() async {}
// Avoid errors caused by flutter upgrade.
// Importing 'package:flutter/widgets.dart' is required.
WidgetsFlutterBinding.ensureInitialized();
// Open the database and store the reference.
final Future<Database> database = openDatabase(
// Set the path to the database. Note: Using the `join` function from the
// `path` package is best practice to ensure the path is correctly
// constructed for each platform.
join(await getDatabasesPath(), 'doggie_database.db'),
);
接下来,你需要创建一个表用以储存各种狗的信息,在此范例中,要创建一个名为dogs
资料库表,它定义了可以被储存的数据。这样,每笔Dog
数据就包含了一个id
,name
和age
,因此,在dogs
数据库表中将有三列,分别是id
,name
和age
id
是int
类型,在资料表中是SQLite的INTEGER
数据类型,推荐将id
作为资料库表的主键,用以改善查询和修改的时间,另外name
是Dart的String
类型,在资料表中是SQLite的TEXT
数据类型,age
也是Dart的int
类型,在资料表中是SQLite的INTEGER
数据类型
关於SQLite资料库能够储存的更多资料类型请查阅官方的 SQLite Datatypes文档
final Future<Database> database = openDatabase(
// Set the path to the database. Note: Using the `join` function from the
// `path` package is best practice to ensure the path is correctly
// constructed for each platform.
join(await getDatabasesPath(), 'doggie_database.db'),
// When the database is first created, create a table to store dogs.
onCreate: (db, version) {
// Run the CREATE TABLE statement on the database.
return db.execute(
"CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)",
);
},
// Set the version. This executes the onCreate function and provides a
// path to perform database upgrades and downgrades.
version: 1,
);
要在dogs
资料表中插入一笔Dog
的资料,需要分为以下两步:
把Dog
转换成一个Map
资料类型
使用insert()
方法把Map
保存到`dogs资料表中
// Update the Dog class to include a `toMap` method.
class Dog {
final int id;
final String name;
final int age;
Dog({this.id, this.name, this.age});
// Convert a Dog into a Map. The keys must correspond to the names of the
// columns in the database.
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
'age': age,
};
}
}
// Define a function that inserts dogs into the database
Future<void> insertDog(Dog dog) async {
// Get a reference to the database.
final Database db = await database;
// Insert the Dog into the correct table. You might also specify the
// `conflictAlgorithm` to use in case the same dog is inserted twice.
//
// In this case, replace any previous data.
await db.insert(
'dogs',
dog.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
// Create a Dog and add it to the dogs table.
final fido = Dog(
id: 0,
name: 'Fido',
age: 35,
);
await insertDog(fido);
现在已经有了一笔Dog
资料存在资料库里,你可以通过查询资料库,检索到一只狗的资料或者所有狗的资料,分为以下两步:
dogs
表对像的query
方法,这将返回一个List <Map>
List<Map>
转换成List<Dog>
资料类型// A method that retrieves all the dogs from the dogs table.
Future<List<Dog>> dogs() async {
// Get a reference to the database.
final Database db = await database;
// Query the table for all The Dogs.
final List<Map<String, dynamic>> maps = await db.query('dogs');
// Convert the List<Map<String, dynamic> into a List<Dog>.
return List.generate(maps.length, (i) {
return Dog(
id: maps[i]['id'],
name: maps[i]['name'],
age: maps[i]['age'],
);
});
}
使用sqflite
package中的update()
方法,可以对已经插入到资料库中的数据进行修改(更新)
修改数据操作包含以下两步:
Map
资料类型;where
语句定位到具体将要被修改的资料Future<void> updateDog(Dog dog) async {
// Get a reference to the database.
final db = await database;
// Update the given Dog.
await db.update(
'dogs',
dog.toMap(),
// Ensure that the Dog has a matching id.
where: "id = ?",
// Pass the Dog's id as a whereArg to prevent SQL injection.
whereArgs: [dog.id],
);
}
// Update Fido's age.
await updateDog(Dog(
id: 0,
name: 'Fido',
age: 42,
));
// Print the updated results.
print(await dogs()); // Prints Fido with age 42.
使用
whereArgs
将参数传递给where
语句,有助於防止SQL注入攻击这里请勿使用字串插补 (String interpolation),比如:
where: "id = ${dog.id}"
!
除了插入和修改狗狗们的数据,你还可以从资料库中删除狗的数据。删除数据用到了sqflite
package中的delete()
方法。
在此范例,新建一个方法用来接收一个id
并且删除资料库中与这个id
匹配的那一笔资料。为了达到这个目的,你必须使用where
语句限定哪一笔才是要被删除的资料
Future<void> deleteDog(int id) async {
// Get a reference to the database.
final db = await database;
// Remove the Dog from the Database.
await db.delete(
'dogs',
// Use a `where` clause to delete a specific dog.
where: "id = ?",
// Pass the Dog's id as a whereArg to prevent SQL injection.
whereArgs: [id],
);
}
完整的Sqlite 范例:
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
void main() async {
// Avoid errors caused by flutter upgrade.
// Importing 'package:flutter/widgets.dart' is required.
WidgetsFlutterBinding.ensureInitialized();
// Open the database and store the reference.
final Future<Database> database = openDatabase(
// Set the path to the database. Note: Using the `join` function from the
// `path` package is best practice to ensure the path is correctly
// constructed for each platform.
join(await getDatabasesPath(), 'doggie_database.db'),
// When the database is first created, create a table to store dogs.
onCreate: (db, version) {
return db.execute(
"CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)",
);
},
// Set the version. This executes the onCreate function and provides a
// path to perform database upgrades and downgrades.
version: 1,
);
Future<void> insertDog(Dog dog) async {
// Get a reference to the database.
final Database db = await database;
// Insert the Dog into the correct table. Also specify the
// `conflictAlgorithm`. In this case, if the same dog is inserted
// multiple times, it replaces the previous data.
await db.insert(
'dogs',
dog.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
Future<List<Dog>> dogs() async {
// Get a reference to the database.
final Database db = await database;
// Query the table for all The Dogs.
final List<Map<String, dynamic>> maps = await db.query('dogs');
// Convert the List<Map<String, dynamic> into a List<Dog>.
return List.generate(maps.length, (i) {
return Dog(
id: maps[i]['id'],
name: maps[i]['name'],
age: maps[i]['age'],
);
});
}
Future<void> updateDog(Dog dog) async {
// Get a reference to the database.
final db = await database;
// Update the given Dog.
await db.update(
'dogs',
dog.toMap(),
// Ensure that the Dog has a matching id.
where: "id = ?",
// Pass the Dog's id as a whereArg to prevent SQL injection.
whereArgs: [dog.id],
);
}
Future<void> deleteDog(int id) async {
// Get a reference to the database.
final db = await database;
// Remove the Dog from the database.
await db.delete(
'dogs',
// Use a `where` clause to delete a specific dog.
where: "id = ?",
// Pass the Dog's id as a whereArg to prevent SQL injection.
whereArgs: [id],
);
}
var fido = Dog(
id: 0,
name: 'Fido',
age: 35,
);
// Insert a dog into the database.
await insertDog(fido);
// Print the list of dogs (only Fido for now).
print(await dogs());
// Update Fido's age and save it to the database.
fido = Dog(
id: fido.id,
name: fido.name,
age: fido.age + 7,
);
await updateDog(fido);
// Print Fido's updated information.
print(await dogs());
// Delete Fido from the database.
await deleteDog(fido.id);
// Print the list of dogs (empty).
print(await dogs());
}
class Dog {
final int id;
final String name;
final int age;
Dog({this.id, this.name, this.age});
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
'age': age,
};
}
// Implement toString to make it easier to see information about
// each dog when using the print statement.
@override
String toString() {
return 'Dog{id: $id, name: $name, age: $age}';
}
}
/*印出
[Dog{id: 0, name: Fido, age: 35}]
[Dog{id: 0, name: Fido, age: 42}]
[]
*/
如果要储存的资料较少,我们则可以用shared_preferences
插件,来将资料存在我们的手机等硬体里
shared_preferences
插件可以把key-value的资料保存到手机等硬体中,并通过封装iOS上的NSUserDefaults
和Android上的SharedPreferences
为简单的数据提供持久化储存使用key-value虽然方便,但它仅限用於基本资料类型:
int
、double
、bool
、string
和stringList
,还有它并不适用於大量资料的存取
首先添加依赖:shared_preferences
...
dependencies:
shared_preferences: ^0.5.12
...
使用SharedPreferences
类的setter方法,Setter方法可用於各种基本资料类型,例如setInt
、setBool
和setString
// obtain shared preferences
final prefs = await SharedPreferences.getInstance();
// set value
prefs.setInt('counter', counter);
使用SharedPreferences
类相应的getter方法。对於每一个setter方法都有对应的getter方法。例如,你可以使用getInt
、getBool
和getString
方法
final prefs = await SharedPreferences.getInstance();
// Try reading data from the counter key. If it doesn't exist, return 0.
final counter = prefs.getInt('counter') ?? 0;
删除资料
使用remove()
方法删除数据
final prefs = await SharedPreferences.getInstance();
prefs.remove('counter');
完整的key-value 范例:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of the application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Shared preferences demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Shared preferences demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
@override
void initState() {
super.initState();
_loadCounter();
}
//Loading counter value on start
_loadCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_counter = (prefs.getInt('counter') ?? 0);
});
}
//Incrementing counter after click
_incrementCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_counter = (prefs.getInt('counter') ?? 0) + 1;
prefs.setInt('counter', _counter);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
有时候我们会需要在local 端的文件做读写的操作,常见於App启动期间产生的持久化数据,或者从网络下载的数据要供离线使用
为了将文件保存到手机等硬体上,你需要结合使用 dart:io
库中的path_provider
这个package
首先添加依赖:path_provider
...
dependencies:
path_provider: ^1.6.18
...
范例:
我们将会显示一个计数器,当计数器发生变化时,你将在硬体中写入资料,以便在App加载时重新读取这些数据
path_provider
package 提供一种与平台无关的方式,以一致的方式访问设备的文件位置系统。该plugin 当前支持访问两种文件位置系统:
Temporary directory (临时文件夹):
这是一个系统可以随时清空的临时(缓存)文件夹。在iOS上对应
NSCachesDirectory
的返回值;在Android上对应getCacheDir()
的返回值Documents directory (Documents目录):
仅供app 使用,用於储存只能由该app使用的文件。只有在删除app时,系统才会清除这个目录。在iOS上,这个目录对应於
NSDocumentDirectory
。在Android上,则是AppData
目录
找到正确的本地路径
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
创建一个指向文件位置的 reference
Future<File> get _localFile async { //使用dart:io库的File类来实现
final path = await _localPath;
return File('$path/counter.txt');
}
将资料写入文件
现在你已经有了可以使用的File
,接下来就可以使用这个文件来读写数据,首先,将一些数据写入该文件。由於使用了计数器,因此只需将整数存为字串格式,然後使用'$counter'
即可调用
Future<File> writeCounter(int counter) async {
final file = await _localFile;
// Write the file.
return file.writeAsString('$counter');
}
从文件读取资料
Future<int> readCounter() async {
try {
final file = await _localFile;
// Read the file.
String contents = await file.readAsString();
return int.parse(contents);
} catch (e) {
// If encountering an error, return 0.
return 0;
}
}
完整的计数器读取、写入文件范例:
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
void main() {
runApp(
MaterialApp(
title: 'Reading and Writing Files',
home: FlutterDemo(storage: CounterStorage()),
),
);
}
class CounterStorage {
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/counter.txt');
}
Future<int> readCounter() async {
try {
final file = await _localFile;
// Read the file
String contents = await file.readAsString();
return int.parse(contents);
} catch (e) {
// If encountering an error, return 0
return 0;
}
}
Future<File> writeCounter(int counter) async {
final file = await _localFile;
// Write the file
return file.writeAsString('$counter');
}
}
class FlutterDemo extends StatefulWidget {
final CounterStorage storage;
FlutterDemo({Key key, @required this.storage}) : super(key: key);
@override
_FlutterDemoState createState() => _FlutterDemoState();
}
class _FlutterDemoState extends State<FlutterDemo> {
int _counter;
@override
void initState() {
super.initState();
widget.storage.readCounter().then((int value) {
setState(() {
_counter = value;
});
});
}
Future<File> _incrementCounter() {
setState(() {
_counter++;
});
// Write the variable as a string to the file.
return widget.storage.writeCounter(_counter);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Reading and Writing Files')),
body: Center(
child: Text(
'Button tapped $_counter time${_counter == 1 ? '' : 's'}.',
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
<<: Day29 语法改革!零基础新手也能读懂的JS - JS30-26 Stripe Follow Along Nav
>>: Laravel 实战经验分享 - Day29 剩下最後的两篇,该讲些什麽呢?
最後倒数10天真的是什麽状况都有老婆下雨骑车雷铲,原定在家写的 实做LAB文章只能在医院用手机以注音...
HTML 语法简易介绍 HTML 是 Hypertext Markup Language 的缩写,也...
前面我们有大概提到Enzyme的优点及作用~ 这篇我们要直接来安装Enzyme和导入Enzyme来供...
前言 在上一个范例中,是写死回传的内容,显然在现实生活中应该是不会有公司让你可以这样做的,而当我们的...
今天要分享的是G-mail寄信的另外一种方式, 虽然比较麻烦, 但是比较安全. 解除人机验证锁定 进...