Flutter入门

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数量,有ProxyProviderProxyProvider2ProxyProvider3…。末尾的数字表示以来的数量。

多个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中的状态呢?有两种方法:

  1. 使用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(),
);
  1. 使用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方法来设置键值对,比如setIntsetBoolsetString

prefs.setInt('counter', counter);

通过类的getter来获取值。

final counter = prefs.getInt('counter') ?? 0;

使用 remove() 方法删除数据。

prefs.remove('counter');

限制

  • 只能用于基本数据类型: intdoubleboolstringstringList
  • 不适用于大量数据的存储。

文件管理

比起存储键值对,直接存储一个文件相对更加灵活,并且支持大量数据存储。

为了将文件保存到磁盘,你需要结合使用 dart:io 库中的 path_provider 这个 package。

path_provider提供了两种文件位置的访问:

  1. 临时文件夹:
    这是一个系统可以随时清空的临时(缓存)文件夹。在 iOS 上对应 NSCachesDirectory 的返回值;在 Android 上对应 getCacheDir() 的返回值。

  2. 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的语句。具体请参考官方教程

参考

上一篇 新手也能看懂的虚拟滚动实现方法
下一篇 Flutter实战经验