目錄
前言
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