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});

  @override
  MyAppState createState() => MyAppState();
}

class MyAppState extends State<MyApp> {
  ThemeMode _themeMode = ThemeMode.light;

  @override
  void 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;
    });
  }

  @override
  Widget 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});

  @override
  Widget 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 file
  return file.writeAsString('$counter');
}

4.从文件中读取数据

        我们使用File类获取文件中的数据。

Future<int> readCounter() async {
  try {
    final file = await _localFile;

    // Read the file
    final contents = await file.readAsString();

    return int.parse(contents);
  } catch (e) {
    // If encountering an error, return 0
    return 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 file
      final contents = await file.readAsString();

      return int.parse(contents);
    } catch (e) {
      // If encountering an error, return 0
      return 0;
    }
  }

  Future<File> writeCounter(int counter) async {
    final file = await _localFile;

    // Write the file
    return file.writeAsString('$counter');
  }
}

class FlutterDemo extends StatefulWidget {
  const FlutterDemo({super.key, required this.storage});

  final CounterStorage storage;

  @override
  State<FlutterDemo> createState() => _FlutterDemoState();
}

class _FlutterDemoState extends State<FlutterDemo> {
  int _counter = 0;

  @override
  void 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);
  }

  @override
  Widget 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});

  @override
  Widget 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;

  @override
  void 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();
  }

  @override
  Widget 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.mfbz.cn/a/763340.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

mac软件卸载后的残留文件删除 mac如何卸载应用程序

很多人都不知道&#xff0c;mac使用系统方式卸载后会有残留文件未被删除&#xff0c;久而久之就会占用大量的磁盘空间。今天小编就来教大家如何删除mac软件卸载后的残留文件&#xff0c;如果你想不留痕迹的删除&#xff0c;mac又该如何正确卸载应用程序&#xff0c;本文将一一为…

整合、速通 版本控制器-->Git 的实际应用

目录 版本控制器 -- Git1、Git 和 SVN 的区别2、Git 的卸载和安装2-1&#xff1a;Git 卸载1、先查下原本的Git版本2、删除环境变量3、控制面板卸载 Git 2-2&#xff1a;Git 下载安装1、官网下载2、详细安装步骤3、安装成功展示 3、Git 基础知识3-1&#xff1a;基本的 Linux 命令…

通俗易懂的chatgpg的原理简介

目录 一、深度学习与语言模型 二、ChatGPT训练三步走 三、情景学习与思维链 四、修改提示语优化结果 五、能力评估和注意问题 六.算法原理 简介&#xff1a; ChatGPT的人工智能原理主要基于深度学习技术&#xff0c;特别是大规模的预训练语言模型和Transformer结构。Cha…

SpringCloud_Eureka注册中心

概述 Eureka是SpringCloud的注册中心。 是一款基于REST的服务治理框架&#xff0c;用于实现微服务架构中的服务发现和负载均衡。 在Eureka体系中&#xff0c;有两种角色: 服务提供者和服务消费者。 服务提供者将自己注册到Eureka服务器&#xff0c;服务消费者从Eureka服务器中…

使用Qt制作一个简单的界面

1、创建工程 步骤一&#xff1a; 步骤二&#xff1a; 步骤三&#xff1a; 选择 build system&#xff0c;有qmake、CMake 和 Qbs 三个选项。 CMake 很常用&#xff0c;功能也很强大&#xff0c;许多知名的项目都是用它&#xff0c;比如 OpenCV 和 VTK&#xff0c;但它的语法繁…

【Android面试八股文】什么是ANR?如何分析和定位ANR?如何避免ANR?

文章目录 一、ANR概述二、触发ANR的主要场景三、Android四大组件中的潜在的ANR风险五、避免ANR的实践建议六、ANR的产生原因与出现的场景6.1 原因:6.2 出现场景:七、ANR的定位与分析7.1. ANR分析思路——traces7.2 ANR其他分析思路与相关日志7.2.1 分析logcat思路7.2.2 分析k…

Spring Cloud Circuit Breaker基础入门与服务熔断

官网地址&#xff1a;https://spring.io/projects/spring-cloud-circuitbreaker#overview 本文SpringCloud版本为&#xff1a; <spring.boot.version>3.1.7</spring.boot.version> <spring.cloud.version>2022.0.4</spring.cloud.version>【1】Circu…

易校网校园综合跑腿小程序源码修复运营版

简介&#xff1a; 易校网校园综合跑腿小程序源码修复运营版&#xff0c;带服务端客户端前端文档说明。 源码安装方法&#xff1a; 需要准备小程序服务号 服务器 备案域名 校园网跑腿小程序源码需要准备 1.小程序 2.服务器&#xff08;推荐配置2h4g3m&#xff09; 3.域名…

【Python实战因果推断】13_线性回归的不合理效果3

目录 Regression Theory Single Variable Linear Regression Multivariate Linear Regression Frisch-Waugh-Lovell Theorem and Orthogonalization Regression Theory 我不打算太深入地探讨线性回归是如何构建和估计的。不过&#xff0c;一点点理论知识将有助于解释线性回归…

更新!谷歌倾斜摄影OSGB数据V1.2版

谷歌倾斜摄影OSGB数据V1.2版终于来了&#xff01; 一个月前发布了谷歌倾斜摄影数据生成OSGB数据V1.0版&#xff0c;对谷歌倾斜摄影数据转换工具进行了重大更新&#xff0c;V1.1版主要解决了三个问题&#xff1a;1.支持Cesiumlab等数据处理软件&#xff0c;将OSGB数据转换成3DTi…

OFDM关键技术——PAPR降低技术

OFDM信号的峰均比问题 PAR问题由于不同子载波上N个正弦信号叠加引起&#xff0c;由于各个子载波的幅值和相位相互独立&#xff0c;当子载波数目较大时&#xff0c;由中心极限定理可知&#xff0c;同相分量的幅度服从高斯分布。 峰值功率&#xff1a;0.1033 平均功率&am…

网安小贴士(4)哈希函数

一、前言 哈希函数是密码学中的基础工具&#xff0c;哈希函数在密码学中扮演着至关重要的角色&#xff0c;广泛应用于确保数据的安全性和完整性。随着技术的发展&#xff0c;新的哈希算法和应用场景也在不断出现。 二、定义 哈希函数是一种数学函数&#xff0c;它接受一个输…

计算机I/O系统与外围设备详解:从基础概念到实际应用

计算机I/O系统与外围设备详解&#xff1a;从基础概念到实际应用 在计算机世界中&#xff0c;理解I/O系统和外围设备的基本概念对初学者来说至关重要。本文将详细介绍I/O系统的基础知识、I/O接口、计算机外围设备及其工作原理&#xff0c;帮助基础小白更好地理解这些概念。 I/O…

毫米波雷达深度学习技术-1.7训练一个神经网络

1.7 训练一个神经网络 对于训练神经网络&#xff0c;有两个步骤&#xff0c;即前向传递和误差反向传播。 1.7.1 前向传播和反向传播 在前向传递中&#xff0c;输入被馈送到模型并与权重向量相乘&#xff0c;并为每一层添加偏差以计算模型的输出。密集层或全连接层第l层的输入、…

中画幅巡检相机-SHARE 100M A10

【毫厘之间&#xff0c;洞见非凡】 ——SHARE 100M A10中画幅测量相机&#xff0c;巡检行业的新选择 在巡检行业&#xff0c;精准度是关键&#xff0c;深圳赛尔智控科技有限公司最新推出的SHARE 100M A10中画幅测量相机&#xff0c;基于先进的IMX461影像传感器&#xff0c;拥有…

prometheus 安装node_exporter, node_exporter 安装最新版 普罗米修思安装监控服务器client

1. 本文介绍两种安装方式&#xff0c;一种安装为service,使用systemctl start node_exporter管理&#xff0c;第二种为安装docker内 容器内使用。 1.1 安装到系统内&#xff1a; 1.1.1 github地址&#xff1a; Releases prometheus/node_exporter GitHub ​ 1.1.2 下载命…

解析Linux top 命令输出并生成动态图表

文章目录 0. 引言1. 原理2. 功能3. 程序架构流程图结构图 4. 数据解析模块5. 图表绘制模块6. 主程序入口7. 使用方法8. 总结9. 附录完整代码 0. 引言 在性能调优和系统监控中&#xff0c;top 命令是一种重要工具&#xff0c;提供了实时的系统状态信息&#xff0c;如 CPU 使用率…

PHP电商系统开发指南高级技巧

开发高级 php 电商系统所需的技巧包括&#xff1a;数据库优化&#xff1a;使用索引、规范化数据结构和缓存机制。性能优化&#xff1a;启用页面缓存、优化图像和使用 cdn。购物车管理&#xff1a;使用会话或数据库存储数据&#xff0c;实现实时更新和弃单恢复。支付集成&#x…

windows10如何打开开发者模式

按键盘上的win键或者点击屏幕左下角的开始图标&#xff0c;即可出现如下的界面 在打开的界面中找到设置按钮&#xff0c;点击设置按钮 进入windows设置界面后&#xff0c;找到‘更新和安全’的选项&#xff0c;随后点击进入 进去后在左侧的功能列表中找到‘开发者选…

Transformer模型原理细节解析

基本原理: Transformer 的核心概念是 自注意力机制(Self-Attention Mechanism),它允许模型在处理每个输入时“关注”输入序列的不同部分。这种机制让模型能够理解每个单词或符号与其他单词或符号之间的关系,而不是逐个地线性处理输入。 Transformer 主要由两个部分组成:…