Flutter入门
I. Provider使用
Provider
作为官网教程中建议的简单状态管理方法。有几个使用注意事项。
状态层级
由于应用状态一般会有较多widget
会用到,所以状态的层级一般较高。类似于变量作用域,我们只有提升变量的作用域,才能确保需要它的widget
都能够访问得到。
而Provider
提供了我们状态的管理。我们可以将状态放入Provider
中,供子孙widget
调用。
void main() {
runApp(
Provider(
create: (_) => MyModel(),
child: ...
)
);
}
这里可以看到,我们将model
放在了最顶层,所以所有的widget
都能访问到MyModel
里面的状态。
一般Provider
最基础的Provider
只提供状态的存储。状态的变更不会触发任何事件,也就是说它只适合那些不会变化的状态。
响应Provider
ChangeNotifierProvider
提供了能够响应状态变更的Provider
。我们需要传入一个继承了ChangeNotify
类的状态。并且在状态变更的操作中调用notifyListeners()
来通知变更。
class CountModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CountModel(),
child: ...
)
);
}
依赖Provider
以上的Provider
,状态都是独立存在的。有时候我们的Model
会依赖于其他的Model
,这个时候,我们需要使用ProxyProvider
来指明依赖的Model
,并且根据依赖的Model
数量,有ProxyProvider
,ProxyProvider2
,ProxyProvider3
…。末尾的数字表示以来的数量。
多个Provider
当同时存在多个Model
时,我们需要相应的创建多个Provider
,这个时候就需要用到MultiProvider
。
MultiProvider(
providers: [
Provider<Something>(create: (_) => Something()),
Provider<SomethingElse>(create: (_) => SomethingElse()),
Provider<AnotherThing>(create: (_) => AnotherThing()),
],
child: someWidget,
)
这样,someWidget
就能用到所有这些Model
状态。
使用model
在介绍了如何声明,管理应用状态。我们还需要知道怎么去使用这些状态。
在子孙widget
中,我们怎么拿到放在Provider
中的状态呢?有两种方法:
- 使用
Consumer
在使用了ChangeNotifierProvider
之后,我们的应用状态支持变更通知。而相应通知,则需要Consumer
。
return Consumer<CartModel>(
builder: (context, cart, child) => Stack(
children: [
// Use SomeExpensiveWidget here, without rebuilding every time.
child,
Text("Total price: ${cart.totalPrice}"),
],
),
// Build the expensive widget here.
child: SomeExpensiveWidget(),
);
- 使用
Provider.of
var cart = Provider.of<CartModel>(context);
当变更状态,但是我们不想重新构建widget
时,
Provider.of<CartModel>(context, listen: false).changeState()
II. 持久化存储
简单数据存储用shared_preferences
如果数据较为简单,比如键值对,可以使用shared_perferences
来存储。它通过封装 iOS 上的 NSUserDefaults 和 Android 上的 SharedPreferences 为简单数据提供持久化存储。
首先获取操作实例:
final perfs = await SharedPreferences().getInstance();
增删改查
通过setter
方法来设置键值对,比如setInt
,setBool
,setString
。
prefs.setInt('counter', counter);
通过类的getter
来获取值。
final counter = prefs.getInt('counter') ?? 0;
使用 remove()
方法删除数据。
prefs.remove('counter');
限制
- 只能用于基本数据类型:
int
、double
、bool
、string
和stringList
。 - 不适用于大量数据的存储。
文件管理
比起存储键值对,直接存储一个文件相对更加灵活,并且支持大量数据存储。
为了将文件保存到磁盘,你需要结合使用 dart:io 库中的 path_provider 这个 package。
path_provider
提供了两种文件位置的访问:
-
临时文件夹:
这是一个系统可以随时清空的临时(缓存)文件夹。在 iOS 上对应 NSCachesDirectory 的返回值;在 Android 上对应 getCacheDir() 的返回值。 -
Documents 目录:
供应用使用,用于存储只能由该应用访问的文件。只有在删除应用时,系统才会清除这个目录。在 iOS 上,这个目录对应于 NSDocumentDirectory。在 Android 上,则是 AppData 目录。
获取本地文件存储路径
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<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;
}
}
使用数据库
前面提到的shared_perferences
只适合存储简单数据,文件存储读取和数据解析太复杂。有没有啥适合大量数据存储又支持自定义数据结构的?答案就是使用SQLite
。
Flutter应用程序中可以通过 sqflite package 来使用 SQLite 数据库。
打开数据库
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
// ...
// 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'),
);
创建表
通过打开数据可以时,在onCreate
钩子中创建我们想要创建的表。
final Future<Database> database = openDatabase(
// Set the path to the database.
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,
);
之后就可以对这张表进行增删改查。而用到的,全是SQLite
的语句。具体请参考官方教程。