目录
前言
1.添加依赖
2.保存数据
3.读取数据
4.移除数据
5.Shared_preferences的优缺点
6.完整的示例代码
二、path_provider
1.导入path_provider
2.创建文件读写的目录
3.向文件中写入数据
4.从文件中读取数据
5.完整的示例代码
三、sqlite数据库
1.导入sqlite
2.创建数据库助手类
3.完整示例代码
四、参考博客
前言
这篇文章主要介绍下Flutter中本地数据持久化的几种方式。
一、shared_preferences
如果你要存储的键值集合相对较少,则可以用 shared_preferences 插件。
当我们要存储一些简单的数据,例如app的系统设置,一些简单的用户信息等,可以考虑使用shared_preferences插件。
我们以一个切换主题的Demo为例,看一下shared_preferences的用法。
1.添加依赖
pubspec.yaml
pubspec.yaml文件中添加shared_preferences依赖。
终端运行pub get命令,安装shared_preferences插件。
pub get
2.保存数据
Flutter中我们通过ThemeMode对象获取当前的主题模式。它是一个枚举类型,有system,light,dark三个主题。
要存储数据,请使用 SharedPreferences
类的 setter 方法。 Setter方法可用于各种基本数据类型,例如 setInt
、setBool
和 setString
。
Setter 方法做两件事:首先,同步更新 key-value 到内存中,然后保存到磁盘中。
在使用shared_preferences保存当前主题的时候,首先创建一个SharedPreferences对象,然后使用一个bool值表示当前的主题类型,调用setBool方法把当前的主题保存到内存中。
Future<void> _toggleTheme() async {SharedPreferences prefs = await SharedPreferences.getInstance();bool isDarkMode = _themeMode == ThemeMode.dark;await prefs.setBool('isDarkMode', !isDarkMode);setState(() {_themeMode = !isDarkMode ? ThemeMode.dark : ThemeMode.light;});}
3.读取数据
当App启动的时候,我们调用SharedPreferences对象的get方法获取上次保存的主题。
要读取数据,请使用 SharedPreferences
类相应的 getter 方法。对于每一个 setter 方法都有对应的 getter 方法。例如,你可以使用 getInt
、getBool
和 getString
方法。
在我们的例子中,我们调用getBool方法获取上次保存的主题。
Future<void> _loadTheme() async {SharedPreferences prefs = await SharedPreferences.getInstance();bool isDarkMode = prefs.getBool('isDarkMode') ?? false;setState(() {_themeMode = isDarkMode ? ThemeMode.dark : ThemeMode.light;});}
4.移除数据
上面的两个方法是SharedPreferences常用的两个方法,当然有时候我们还需要清空保存在内存中的信息,这个时候我们可以调用对象的remove方法,移除保存在内存中的key-value键值对。
final prefs = await SharedPreferences.getInstance();// 移除某个值
await prefs.remove('counter');
5.Shared_preferences的优缺点
shared_preferences的优点就是通过键值对的方式存取数据简单方便,但是也有以下的局限性:
- 只能用于基本数据类型:
int
、double
、bool
、string
和List<String>
。 - 不是为存储大量数据而设计的。
- 不能确保应用重启后数据仍然存在。
6.完整的示例代码
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';void main() {runApp(const MyApp());
}class MyApp extends StatefulWidget {const MyApp({super.key});@overrideMyAppState createState() => MyAppState();
}class MyAppState extends State<MyApp> {ThemeMode _themeMode = ThemeMode.light;@overridevoid initState() {super.initState();_loadTheme();}Future<void> _loadTheme() async {SharedPreferences prefs = await SharedPreferences.getInstance();bool isDarkMode = prefs.getBool('isDarkMode') ?? false;setState(() {_themeMode = isDarkMode ? ThemeMode.dark : ThemeMode.light;});}Future<void> _toggleTheme() async {SharedPreferences prefs = await SharedPreferences.getInstance();bool isDarkMode = _themeMode == ThemeMode.dark;await prefs.setBool('isDarkMode', !isDarkMode);setState(() {_themeMode = !isDarkMode ? ThemeMode.dark : ThemeMode.light;});}@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Theme Demo',theme: ThemeData.light(),darkTheme: ThemeData.dark(),themeMode: _themeMode,home: MyHomePage(themeMode: _themeMode,onThemeChanged: _toggleTheme,),);}
}class MyHomePage extends StatelessWidget {final ThemeMode themeMode;final VoidCallback onThemeChanged;const MyHomePage({super.key, required this.themeMode, required this.onThemeChanged});@overrideWidget build(BuildContext context) {bool isDarkMode = themeMode == ThemeMode.dark;return Scaffold(appBar: AppBar(title: const Text('Theme Demo'),),body: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Center(child: Text(isDarkMode ? "当前模式:暗黑模式" : "当前模式:白天模式",style: const TextStyle(fontSize: 24),),),const SizedBox(height: 20),ElevatedButton(onPressed: onThemeChanged,child: const Text('切换当前模式',style: TextStyle(fontSize: 18),),),],),);}
}
二、path_provider
有时候我们需要把一些数据以文件的形式保存到本地,这个时候path_provider就派上用场了。
path_provider提供一种平台无关的方式以一致的方式访问设备的文件位置系统。该 plugin 当前支持访问两种文件位置系统:
磁盘文件的读写操作可能会相对方便地实现某些业务场景。它常见于应用启动期间产生的持久化数据,或者从网络下载数据供离线使用。
临时文件夹: 这是一个系统可以随时清空的临时(缓存)文件夹。在 iOS 上对应 NSCachesDirectory 的返回值;在 Android 上对应 getCacheDir() 的返回值。
Documents 目录:供应用使用,用于存储只能由该应用访问的文件。只有在删除应用时,系统才会清除这个目录。在 iOS 上,这个目录对应于 NSDocumentDirectory
。在 Android 上,则是 AppData
目录。
我们以计数器为例,当我们点击计时器的时候,把当前的计时器的值保存到文件中。
看看如何实现这个实例:
1.导入path_provider
path_provider: ^2.1.3
2.创建文件读写的目录
以Document目录为例,首先我们确认文件的目录:
import 'package:path_provider/path_provider.dart';
// ···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');
}
3.向文件中写入数据
当我们点击按钮之后,把点击次数的值转成字符串保存到文件中即可。
Future<File> writeCounter(int counter) async {final file = await _localFile;// Write the filereturn file.writeAsString('$counter');
}
4.从文件中读取数据
我们使用File类获取文件中的数据。
Future<int> readCounter() async {try {final file = await _localFile;// Read the filefinal contents = await file.readAsString();return int.parse(contents);} catch (e) {// If encountering an error, return 0return 0;}
}
5.完整的示例代码
import 'dart:async';
import 'dart:io';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 filefinal contents = await file.readAsString();return int.parse(contents);} catch (e) {// If encountering an error, return 0return 0;}}Future<File> writeCounter(int counter) async {final file = await _localFile;// Write the filereturn file.writeAsString('$counter');}
}class FlutterDemo extends StatefulWidget {const FlutterDemo({super.key, required this.storage});final CounterStorage storage;@overrideState<FlutterDemo> createState() => _FlutterDemoState();
}class _FlutterDemoState extends State<FlutterDemo> {int _counter = 0;@overridevoid initState() {super.initState();widget.storage.readCounter().then((value) {setState(() {_counter = value;});});}Future<File> _incrementCounter() {setState(() {_counter++;});// Write the variable as a string to the file.return widget.storage.writeCounter(_counter);}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Reading and Writing Files'),),body: Center(child: Text('Button tapped $_counter time${_counter == 1 ? '' : 's'}.',),),floatingActionButton: FloatingActionButton(onPressed: _incrementCounter,tooltip: 'Increment',child: const Icon(Icons.add),),);}
}
三、sqlite数据库
如果你正在编写一个需要持久化且查询大量本地设备数据的 app,可考虑采用数据库,而不是本地文件夹或关键值库。总的来说,相比于其他本地持久化方案来说,数据库能够提供更为迅速的插入、更新、查询功能。
这个熟悉数据库的同学们对这个应该比较熟悉。我们以一个demos为例,看一下sqlite的用法
1.导入sqlite
和另外两种方式的导入方式差不多,我们在yaml中配置sqlite和path_provider.
path_provider: ^2.1.3
sqflite: ^2.3.3+1
2.创建数据库助手类
这一步,我们创建一个名为database_helper.dart
的文件,用于管理数据库的创建和操作:
import 'dart:async';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';class DatabaseHelper {static final DatabaseHelper _instance = DatabaseHelper._internal();factory DatabaseHelper() => _instance;static Database? _database;DatabaseHelper._internal();Future<Database> get database async {if (_database != null) return _database!;_database = await _initDatabase();return _database!;}Future<Database> _initDatabase() async {String path = join(await getDatabasesPath(), 'demo.db');return await openDatabase(path,version: 1,onCreate: _onCreate,);}Future _onCreate(Database db, int version) async {await db.execute('''CREATE TABLE items (id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT)''');}Future<int> insertItem(String name) async {Database db = await database;return await db.insert('items', {'name': name});}Future<List<Map<String, dynamic>>> getItems() async {Database db = await database;return await db.query('items');}Future<int> deleteItem(int id) async {Database db = await database;return await db.delete('items', where: 'id = ?', whereArgs: [id]);}
}
3.完整示例代码
我们在UI文件中,使用数据库管理类进行数据库的增删改查操作:
import 'package:flutter/material.dart';
import 'database_helper.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});@overrideWidget build(BuildContext context) {return MaterialApp(title: 'SQLite Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: const MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});@override_MyHomePageState createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {final DatabaseHelper _dbHelper = DatabaseHelper();final TextEditingController _controller = TextEditingController();late Future<List<Map<String, dynamic>>> _items;@overridevoid initState() {super.initState();_refreshItems();}void _refreshItems() {setState(() {_items = _dbHelper.getItems();});}void _addItem() async {if (_controller.text.isNotEmpty) {await _dbHelper.insertItem(_controller.text);_controller.clear();_refreshItems();}}void _deleteItem(int id) async {await _dbHelper.deleteItem(id);_refreshItems();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('SQLite Demo'),),body: Column(children: <Widget>[Padding(padding: const EdgeInsets.all(8.0),child: TextField(controller: _controller,decoration: const InputDecoration(labelText: 'Item name',border: OutlineInputBorder(),),),),ElevatedButton(onPressed: _addItem,child: const Text('Add Item'),),Expanded(child: FutureBuilder<List<Map<String, dynamic>>>(future: _items,builder: (context, snapshot) {if (snapshot.connectionState == ConnectionState.waiting) {return const Center(child: CircularProgressIndicator());} else if (snapshot.hasError) {return const Center(child: Text('Error'));} else if (!snapshot.hasData || snapshot.data!.isEmpty) {return const Center(child: Text('No items'));} else {return ListView.builder(itemCount: snapshot.data!.length,itemBuilder: (context, index) {final item = snapshot.data![index];return ListTile(title: Text(item['name']),trailing: IconButton(icon: const Icon(Icons.delete),onPressed: () => _deleteItem(item['id']),),);},);}},),),],),);}
}
四、参考博客
1.shared_preferences
2.sqflite
3.path_provider