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

news/2024/7/8 3:49:41 标签: 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.niftyadmin.cn/n/5536640.html

相关文章

opencv 图像的缩放(放大,缩小),翻转与旋转

目录 opencv 图像的缩放(放大&#xff0c;缩小)&#xff0c;翻转&#xff0c;旋转1、图像的缩放&#xff0c;旋转过程中为什么需要插值&#xff1a;2、常见的插值算法包括&#xff1a;3、图像的缩放&#xff0c;翻转&#xff0c;旋转&#xff1a;&#xff08;1&#xff09;图像…

R 绘图 - 饼图

R 绘图 - 饼图 饼图是一种常用的数据可视化工具&#xff0c;用于展示数据集中各个类别的相对比例。在R语言中&#xff0c;饼图可以通过多种方式绘制&#xff0c;其中最常用的是pie()函数。本文将详细介绍如何在R中创建和定制饼图&#xff0c;包括基本饼图的绘制、添加标签、调…

【安全攻防】网络安全中的序列化与反序列

1.序列化与反序列化 首先要了解序列化与反序列化的定义&#xff0c;以及序列化反序列化所用到的基本函数。 序列化&#xff1a;把对象转换为字节序列的过程称为对象的序列化&#xff0c;相当于游戏中的存档。 PHP中的序列化函数serialize() **serialize()**函数用于序列化对…

Spring:Spring中分布式事务解决方案

一、前言 在Spring中&#xff0c;分布式事务是指涉及多个数据库或系统的事务处理&#xff0c;其中事务的参与者、支持事务的服务器、资源管理器以及事务管理器位于分布式系统的不同节点上。这样的架构使得两个或多个网络计算机上的数据能够被访问并更新&#xff0c;同时将这些操…

adb shell ps -T打印出来参数的含义,以及D,T,Z代表的状态含义是什么?

在Android系统中&#xff0c;使用adb shell ps命令可以查看当前系统中运行的进程信息。当你添加-T选项时&#xff08;注意&#xff0c;标准的ps命令在Android的adb shell中可能不直接支持-T选项&#xff0c;这通常与Linux中的ps命令略有不同&#xff09;&#xff0c;你可能是想…

Docker容器 为MySQL创建新用户和授权

当您需要为 MySQL 数据库创建一个新用户并配置其访问权限时&#xff0c;可以按照以下步骤操作。我将创建一个名为 newuser 的新用户&#xff0c;并为其授予在任何主机上访问所有数据库的权限。 创建新用户和授权步骤&#xff1a; 登录到 MySQL 服务器 首先&#xff0c;使用具有…

UE5的安装与基本操作(一)

文章目录 前言安装UE5新建第一个游戏项目基本游览方式对目标进行变换各种变换对齐 快速定位目标 总结 前言 Unreal Engine 5 (UE5) 是一款由 Epic Games 开发的实时 3D 创作平台&#xff0c;用于制作游戏、电影、动画、建筑可视化和其他类型的交互式体验。UE5 提供了一系列强大…

打印详细讲解

DATA: lv_func_module_name TYPE rs38l_fnam, "打印程序函数名lv_sformname TYPE tdsfname, "smartforms名称ls_control_parameters TYPE ssfctrlop,ls_output TYPE ssfcompop,ls_job_output_info TYPE ssfcrescl,ls_ssfcrespd T…