Flutter本地数据持久化的几种方式

目录

前言

一、shared_preferences

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方法可用于各种基本数据类型,例如 setIntsetBool 和 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 方法。例如,你可以使用 getIntgetBool 和 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的优点就是通过键值对的方式存取数据简单方便,但是也有以下的局限性:

  1. 只能用于基本数据类型: intdoubleboolstring 和 List<String>
  2. 不是为存储大量数据而设计的。
  3. 不能确保应用重启后数据仍然存在。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/41013.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mac本地部署大模型-单机运行

前些天在一台linux服务器&#xff08;8核&#xff0c;32G内存&#xff0c;无显卡&#xff09;使用ollama运行阿里通义千问Qwen1.5和Qwen2.0低参数版本大模型&#xff0c;Qwen2-1.5B可以运行&#xff0c;但是推理速度有些慢。 一直还没有尝试在macbook上运行测试大模型&#xf…

我这个经验好找嵌入式的工作吗?

大家好&#xff0c;我是麦鸽。最近网友的提问&#xff0c;这样的经验&#xff0c;好找嵌入式的工作吗&#xff1f; 下面是网友的情况&#xff1a; 本人目前大二机器人工程&#xff0c;未来想要入职嵌入式行业&#xff0c;有robomaster比赛经验本人负责电控&#xff0c;但是由于…

基因组学系列3:基因分型Phasing与单倍型参考序列HRC

1. 基因分型Phasing概念 基因分型&#xff0c;也称为基因定相、单倍体分型、单倍体构建等&#xff0c;即将一个二倍体&#xff08;或多倍体&#xff09;基因组上的等位基因&#xff08;或杂合位点&#xff09;正确定位到父亲或母亲的染色体上&#xff0c;最终使得来自同一亲本…

相亲交友APP系统婚恋交友社交软件开发语音视频聊天平台定制开发-婚恋相亲交友软件平台介绍——app小程序开发定制

互联网飞速发展的时代&#xff0c;相亲交友软件成为了许多年轻人首选的相亲方式&#xff0c;越来越多的单身男女希望在婚恋交友软件平台上寻找灵魂伴侣&#xff0c;相亲交友软件因此具有很高的市场价值。 多客婚恋相亲交友系统是一款定位高端&#xff0c;到手就能运营的成熟婚恋…

软件测评中心▏软件验收测试方法和测试内容简析

在当今数字化转型的浪潮下&#xff0c;软件验收测试变得越来越重要。软件验收测试&#xff0c;顾名思义&#xff0c;是对软件进行验收的过程中进行的一项测试。它用于确保软件在满足需求、达到预期效果后才能正式交付给客户使用。软件验收测试是一项全面、系统的测试过程&#…

sublime 3 背景和字体颜色修改

sublime 4 突然抽风&#xff0c;每次打开都显示 “plugin_host-3.3 has exited unexpectedly, some plugin functionality won’t be available until Sublime Text has been restarted” 一直没调好&#xff0c;所以我退回到sublime 3了。下载好了软件没问题&#xff0c;但是一…

半导体光电

《半导体光电》创刊于1976年&#xff0c;是由中国电子科技集团公司主管、重庆光电技术研究所&#xff08;中国电子科技集团公司第四十四研究所&#xff09;主办的中文科技期刊。本刊国内外公开发行&#xff0c;经过四十余年的发展已经成为我国光电子专业领域有代表性的刊物。 …

Zabbix 配置grafana对接

zabbix对接grafana简介 Zabbix与Grafana对接可以实现更加丰富和美观的数据可视化&#xff0c;可以让您利用Grafana强大的可视化功能来展示Zabbix收集的数据。 zabbix插件的两种安装方式 使用grafana-cli 命令进行安装在grafana管理页面中进入Administration/Plugins and dat…

2024.7.4学习日报

1、ppt前三章 5日计划 1、至少做到实验 2、java

css中文字书写方向

writing-mode 是 CSS 中的一个属性&#xff0c;用于设置文本、内联元素、表格单元格和表格列的书写方向、文本排列以及块流方向。以下是对 writing-mode 属性的详细介绍&#xff1a; 1. 语法和值 语法&#xff1a;writing-mode: horizontal-tb | vertical-rl | vertical-lr |…

在RT-Thread-Studio中添加arm_math库

1.在CMSIS\Lib\GCC中找到对应的库&#xff0c;如本文使用的libarm_cortexM4lf_math.a。将库拷贝到工程&#xff0c;并做如下图设置。搜索路径为库文件在项目中的实际位置。 2.将CMSIS\DSP\Include下的文件复制到工程目录中&#xff0c;并添加包含路径 3.添加宏定义&#xff0c…

Memcached缓存预热深度解析:加速应用性能的秘诀

Memcached缓存预热深度解析&#xff1a;加速应用性能的秘诀 在高性能计算环境中&#xff0c;Memcached作为一种广泛使用的分布式内存缓存系统&#xff0c;其缓存预热机制对于提升应用性能至关重要。缓存预热可以减少系统启动时的延迟&#xff0c;避免缓存未命中&#xff0c;从…

2806. 取整购买后的账户余额

2806. 取整购买后的账户余额 题目链接&#xff1a;2806. 取整购买后的账户余额 代码如下&#xff1a; class Solution { public:int accountBalanceAfterPurchase(int purchaseAmount) {return 100-(purchaseAmount5)/10*10;} };

QTreeWidget的简单使用

使用 QTreeWidget 实现复杂树控件功能的详细教程_treewidget 加控件-CSDN博客 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <QTreeWidget> namespace Ui { class MainWindow; }class MainWindow : public QMainWindow {Q_OBJECTpu…

阿里巴巴Arthas分析调优JVM实战及常量池详解

目录 一、阿里巴巴Arthas详解 Arthas使用场景 Arthas命令 Arthas使用 二、GC日志详解 如何分析GC日志 CMS G1 GC日志分析工具 三、JVM参数汇总查看命令 四、Class常量池与运行时常量池 字面量 符号引用 五、字符串常量池 字符串常量池的设计思想 三种字符串操作…

墨烯的语言技术栈-C语言基础-005

在VS的安装路径下有一个文件: newcfile.cpp的文件 在VS工程中创建新的.c或者.cpp文件的时候,都是拷贝newcfile.cpp这个文件的! everything工具中 有一个newcfile.cpp 然后打开文件路径在newcfile.cpp 添加#define _CRT_SECURE_NO_WARNINGS替换即可 五.变量的作用域(局部变量…

freemarker生成pdf,同时pdf插入页脚,以及数据量大时批量处理

最近公司有个需求&#xff0c;就是想根据一个模板生成一个pdf文档&#xff0c;当即我就想到了freemarker这个远古老东西&#xff0c;毕竟freemarker在模板渲染方面还是非常有优势的。 准备依赖&#xff1a; <dependency><groupId>org.springframework.boot</gr…

【IDEA】maven如何进行文件导入,配置并打包

一&#xff0c;介绍、安装 1、maven介绍 maven是一个Java世界中&#xff0c;构建工具。 核心功能&#xff1a; (1) 管理依赖&#xff1a; 管理文件运行的顺序逻辑依赖关系。对配置文件&#xff0c;进行构建和编译。其也是在调用jdk&#xff0c;来进行编译打包工作。 (2) 打…

JavaScript中的原型和原型链

一、原型&#xff1a;每个函数都有prototype属性&#xff0c;称之为原型&#xff0c;这个属性也是个对象所以也称之为原型对象。 1.原型可以放一些属性和方法&#xff0c;供实例对象使用。 <body><script>const arr new Array(1,3,5,7,6)document.getElementByI…