《Flutter全栈构建实战指南:从零到高级》- 15 -本地数据存储

Flutter本地存储

当我们关闭App再重新打开,为什么有些数据(比如登录状态、用户设置、文章草稿)还在,而有些数据(比如临时弹窗状态)却消失了?这背后就是 “本地存储” 在发挥作用。可以说,一个不懂得如何管理本地数据的开发者,很难做出用户体验好的应用。

今天,我们就来彻底搞懂Flutter中的本地存储。


一、 为什么需要本地存储?

举个例子:如果你每天醒来都会失忆,不记得自己的名字、家在哪里、昨天做了什么……这简直是一场灾难。对于App而言,本地存储就是它的 “记忆系统”

主要应用场景:

  1. 用户偏好设置:比如主题颜色、语言选择、消息提醒开关。
  2. 登录状态保持:用户登录后,App“记住”他,下次打开无需重新登录。
  3. 缓存网络数据:将首屏数据缓存下来,下次启动秒开,提升用户体验。
  4. 离线数据持久化:如笔记草稿、阅读进度、购物车商品,即使断网也不丢失。
  5. 大数据量结构化存储:比如聊天记录、交易明细等。

Flutter拥有多种本地数据存储方案,下面我们先看下用张图来了解下存储方案脉络:
在这里插入图片描述


二、 shared_preferences

2.1 它是什么?能干什么?

shared_preferences 这个名字听起来有点拗口,但其实很简单。你可以把它理解成 Flutter 为我们在本地提供的一个 “小本子”,专门用来记录一些简单的、键值对形式的数据。

  • shared(共享):指这些数据在你的App内是共享的,任何页面都能读写。
  • preferences(偏好):顾名思义,最适合存储用户的偏好设置。

它的本质是什么?
在 Android 上,它背后是通过 SharedPreferences API 将数据以 XML 文件形式存储;在 iOS 上,则使用的是 NSUserDefaults。Flutter 插件帮我们统一了这两端的接口,让我们可以用一套代码搞定双端存储。

2.2 工作原理图解

让我们看看当你调用 setString('name', '一一') 时,背后发生了什么:

Flutter Appshared_preferences插件Method ChannelAndroid (SharedPreferences)iOS (NSUserDefaults)调用 setString('name', '一一')通过Method Channel调用原生代码(在Android上) 写入XML文件(在iOS上) 写入plist文件写入成功写入成功返回结果返回 Future<bool> (true)Flutter Appshared_preferences插件Method ChannelAndroid (SharedPreferences)iOS (NSUserDefaults)

关键点:

  • 异步操作:所有读写操作都是 Future,意味着不会阻塞你的UI线程。
  • 持久化:数据被写入设备文件系统,App重启后依然存在。
  • 平台差异被屏蔽:你不需要关心底层是XML还是plist,插件帮你处理了。
2.3 下面用一段代码来详细介绍下

第一步:引入依赖
pubspec.yaml 文件中添加:

dependencies:
shared_preferences: ^2.2.2 # 请使用最新版本

然后运行 flutter pub get

第二步:基础CRUD操作

import 'package:shared_preferences/shared_preferences.dart';
class SPManager {
// 单例
static final SPManager _instance = SPManager._internal();
factory SPManager() => _instance;
SPManager._internal();
late SharedPreferences _prefs;
// 初始化
Future<void> init() async {
  _prefs = await SharedPreferences.getInstance();
  print('SharedPreferences 初始化完成!');
  }
  // 1. 写入数据
  Future<bool> saveUserInfo() async {
    try {
    // 字符串
    await _prefs.setString('username', 'Flutter本地存储');
    // 整型
    await _prefs.setInt('userAge', 28);
    // 布尔值
    await _prefs.setBool('isVip', true);
    // 字符串列表
    await _prefs.setStringList('hobbies', ['编程', '读书', '健身']);
    // 双精度浮点数
    await _prefs.setDouble('walletBalance', 99.99);
    print('用户信息保存成功!');
    return true;
    } catch (e) {
    print('保存失败: $e');
    return false;
    }
    }
    // 2. 读取数据
    void readUserInfo() {
    // 读取字符串,提供默认值
    String username = _prefs.getString('username') ?? '未知用户';
    int age = _prefs.getInt('userAge') ?? 0;
    bool isVip = _prefs.getBool('isVip') ?? false;
    double balance = _prefs.getDouble('walletBalance') ?? 0.0;
    List<String> hobbies = _prefs.getStringList('hobbies') ?? [];
      print('''
      用户信息:
      用户名:$username
      年龄:$age
      VIP:$isVip
      余额:$balance
      爱好:$hobbies
      ''');
      }
      // 3. 删除数据
      Future<bool> deleteUserInfo() async {
        try {
        // 删除指定键
        await _prefs.remove('username');
        // 清空所有数据
        // await _prefs.clear();
        print('用户信息已删除');
        return true;
        } catch (e) {
        print('删除失败: $e');
        return false;
        }
        }
        // 4. 检查键是否存在
        bool containsKey(String key) {
        return _prefs.containsKey(key);
        }
        // 5. 获取所有键
        Set<String> getAllKeys() {
          return _prefs.getKeys();
          }
          }

第三步:在App中使用

void main() async {
// 确保WidgetsBinding初始化
WidgetsFlutterBinding.ensureInitialized();
// 初始化SPManager
await SPManager().init();
runApp(MyApp());
}
class MyApp extends StatelessWidget {

Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {

_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
  final SPManager _spManager = SPManager();
  
  Widget build(BuildContext context) {
  return Scaffold(
  appBar: AppBar(title: Text('SP演示')),
  body: Center(
  child: Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
  ElevatedButton(
  onPressed: () => _spManager.saveUserInfo(),
  child: Text('保存用户信息'),
  ),
  ElevatedButton(
  onPressed: () => _spManager.readUserInfo(),
  child: Text('读取用户信息'),
  ),
  ElevatedButton(
  onPressed: () => _spManager.deleteUserInfo(),
  child: Text('删除用户信息'),
  ),
  ],
  ),
  ),
  );
  }
  }
2.4 使用介绍与注意点
  1. 一定要先初始化:在使用前必须调用 getInstance() 并等待其完成。
  2. 处理空值:读取时一定要提供默认值,因为键可能不存在。
  3. 不要存大数据:它不适合存储大型对象或列表,性能会变差。
  4. 键名管理:建议使用常量来管理键名,避免拼写错误。
    class SPKeys {
    static const String username = 'username';
    static const String userAge = 'user_age';
    static const String isVip = 'is_vip';
    }
  5. 异步错误处理:使用 try-catch 包裹可能出错的操作。

三、 文件存储

3.1 适用场景

当你的数据不适合用键值对存储时,文件存储就派上用场了:

  • App的配置文件(JSON, XML)
  • 用户下载的图片、文档
  • 应用日志文件
  • 需要自定义格式的数据
3.2 文件系统路径详解

在Flutter中,我们使用 path_provider 插件来获取各种路径:

import 'package:path_provider/path_provider.dart';
class FilePathManager {
// 获取临时目录
static Future<String> get tempPath async {
  final dir = await getTemporaryDirectory();
  return dir.path;
  }
  // 获取文档目录(Android对应App专用目录,iOS对应Documents)
  static Future<String> get documentsPath async {
    final dir = await getApplicationDocumentsDirectory();
    return dir.path;
    }
    // 获取外部存储目录
    static Future<String?> get externalStoragePath async {
      final dir = await getExternalStorageDirectory();
      return dir?.path;
      }
      // 获取支持目录
      static Future<String> get supportPath async {
        final dir = await getApplicationSupportDirectory();
        return dir.path;
        }
        }

路径选择:

  • 临时文件getTemporaryDirectory() - 缓存,系统可清理
  • 用户数据getApplicationDocumentsDirectory() - 用户生成的内容
  • App内部文件getApplicationSupportDirectory() - App运行所需文件
3.3 文件存储实战演示

一个完整的文件管理类如下代码所示:

import 'dart:io';
import 'dart:convert';
import 'package:path_provider/path_provider.dart';
class FileManager {
// 单例
static final FileManager _instance = FileManager._internal();
factory FileManager() => _instance;
FileManager._internal();
// 获取文件路径
Future<String> _getLocalFilePath(String filename) async {
  final dir = await getApplicationDocumentsDirectory();
  return '${dir.path}/$filename';
  }
  // 1. 写入字符串到文件
  Future<File> writeStringToFile(String content, String filename) async {
    try {
    final file = File(await _getLocalFilePath(filename));
    return await file.writeAsString(content);
    } catch (e) {
    print('写入文件失败: $e');
    rethrow;
    }
    }
    // 2. 从文件读取字符串
    Future<String> readStringFromFile(String filename) async {
      try {
      final file = File(await _getLocalFilePath(filename));
      if (await file.exists()) {
      return await file.readAsString();
      } else {
      throw Exception('文件不存在');
      }
      } catch (e) {
      print('读取文件失败: $e');
      rethrow;
      }
      }
      // 3. 写入JSON对象
      Future<File> writeJsonToFile(Map<String, dynamic> json, String filename) async {
        final jsonString = jsonEncode(json);
        return await writeStringToFile(jsonString, filename);
        }
        // 4. 从文件读取JSON对象
        Future<Map<String, dynamic>> readJsonFromFile(String filename) async {
          try {
          final jsonString = await readStringFromFile(filename);
          return jsonDecode(jsonString);
          } catch (e) {
          print('读取JSON失败: $e');
          rethrow;
          }
          }
          // 5. 增加内容到文件
          Future<File> appendToFile(String content, String filename) async {
            try {
            final file = File(await _getLocalFilePath(filename));
            return await file.writeAsString(content, mode: FileMode.append);
            } catch (e) {
            print('追加文件失败: $e');
            rethrow;
            }
            }
            // 6. 检查文件是否存在
            Future<bool> fileExists(String filename) async {
              final file = File(await _getLocalFilePath(filename));
              return await file.exists();
              }
              // 7. 删除文件
              Future<void> deleteFile(String filename) async {
                try {
                final file = File(await _getLocalFilePath(filename));
                if (await file.exists()) {
                await file.delete();
                print('文件删除成功: $filename');
                }
                } catch (e) {
                print('删除文件失败: $e');
                rethrow;
                }
                }
                // 8. 获取文件信息
                Future<FileStat> getFileInfo(String filename) async {
                  try {
                  final file = File(await _getLocalFilePath(filename));
                  if (await file.exists()) {
                  return await file.stat();
                  } else {
                  throw Exception('文件不存在');
                  }
                  } catch (e) {
                  print('获取文件信息失败: $e');
                  rethrow;
                  }
                  }
                  }
3.4 以用户配置管理为例:
class UserConfigManager {
static const String _configFileName = 'user_config.json';
final FileManager _fileManager = FileManager();
// 保存用户配置
Future<void> saveUserConfig({
  required String theme,
  required String language,
  required bool darkMode,
  required List<String> recentSearches,
    }) async {
    final config = {
    'theme': theme,
    'language': language,
    'darkMode': darkMode,
    'recentSearches': recentSearches,
    'lastUpdated': DateTime.now().toIso8601String(),
    };
    await _fileManager.writeJsonToFile(config, _configFileName);
    print('用户配置已保存');
    }
    // 读取用户配置
    Future<Map<String, dynamic>> loadUserConfig() async {
      try {
      if (await _fileManager.fileExists(_configFileName)) {
      return await _fileManager.readJsonFromFile(_configFileName);
      } else {
      // 返回默认配置
      return _getDefaultConfig();
      }
      } catch (e) {
      print('加载用户配置失败,使用默认配置: $e');
      return _getDefaultConfig();
      }
      }
      Map<String, dynamic> _getDefaultConfig() {
        return {
        'theme': 'light',
        'language': 'zh-CN',
        'darkMode': false,
        'recentSearches': [],
        'lastUpdated': DateTime.now().toIso8601String(),
        };
        }
        // 清空配置
        Future<void> clearConfig() async {
          await _fileManager.deleteFile(_configFileName);
          }
          }

四、 SQLite

4.1 什么是SQLite?为什么需要它?

SQLite是一个轻量级的、文件式的关系型数据库。它不需要单独的服务器进程,整个数据库就是一个文件,非常适合移动端应用。

使用场景:

  • 用户关系管理(联系人、好友)
  • 商品目录、订单管理
  • 聊天消息记录
  • 任何需要复杂查询和关系的数据
4.2 Flutter中的SQLite架构

在Flutter中,我们通常使用 sqflite 插件来操作SQLite:

Flutter App
sqflite插件
Method Channel
Android: SQLiteDatabase
iOS: SQLite3 Library
.db文件
数据持久化
4.3 构建一个任务管理App

第一步:添加依赖

dependencies:
sqflite: ^2.3.0
path: ^1.8.3

第二步:创建数据库工具类

import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class DatabaseHelper {
static final DatabaseHelper _instance = DatabaseHelper._internal();
factory DatabaseHelper() => _instance;
DatabaseHelper._internal();
static Database? _database;
// 数据库名称和版本
static const String _dbName = 'task_manager.db';
static const int _dbVersion = 1;
// 表名和列名
static const String tableTasks = 'tasks';
static const String columnId = 'id';
static const String columnTitle = 'title';
static const String columnDescription = 'description';
static const String columnIsCompleted = 'is_completed';
static const String columnCreatedAt = 'created_at';
static const String columnUpdatedAt = 'updated_at';
// 获取数据库实例
Future<Database> get database async {
  if (_database != null) return _database!;
  _database = await _initDatabase();
  return _database!;
  }
  // 初始化数据库
  Future<Database> _initDatabase() async {
    // 获取数据库路径
    String path = join(await getDatabasesPath(), _dbName);
    // 创建/打开数据库
    return await openDatabase(
    path,
    version: _dbVersion,
    onCreate: _createTables,
    onUpgrade: _upgradeDatabase,
    );
    }
    // 创建表
    Future<void> _createTables(Database db, int version) async {
      await db.execute('''
      CREATE TABLE $tableTasks (
      $columnId INTEGER PRIMARY KEY AUTOINCREMENT,
      $columnTitle TEXT NOT NULL,
      $columnDescription TEXT,
      $columnIsCompleted INTEGER NOT NULL DEFAULT 0,
      $columnCreatedAt INTEGER NOT NULL,
      $columnUpdatedAt INTEGER NOT NULL
      )
      ''');
      print('任务表创建成功!');
      }
      // 数据库升级
      Future<void> _upgradeDatabase(Database db, int oldVersion, int newVersion) async {
        if (oldVersion < 2) {
        // await db.execute('ALTER TABLE $tableTasks ADD COLUMN new_column TEXT');
        }
        print('数据库从版本 $oldVersion 升级到 $newVersion');
        }
        // 关闭数据库
        Future<void> close() async {
          if (_database != null) {
          await _database!.close();
          _database = null;
          }
          }
          }

第三步:创建数据模型

class Task {
int? id;
String title;
String? description;
bool isCompleted;
DateTime createdAt;
DateTime updatedAt;
Task({
this.id,
required this.title,
this.description,
this.isCompleted = false,
DateTime? createdAt,
DateTime? updatedAt,
})  : createdAt = createdAt ?? DateTime.now(),
updatedAt = updatedAt ?? DateTime.now();
// 将Task对象转换为Map,便于存入数据库
Map<String, dynamic> toMap() {
  return {
  DatabaseHelper.columnId: id,
  DatabaseHelper.columnTitle: title,
  DatabaseHelper.columnDescription: description,
  DatabaseHelper.columnIsCompleted: isCompleted ? 1 : 0,
  DatabaseHelper.columnCreatedAt: createdAt.millisecondsSinceEpoch,
  DatabaseHelper.columnUpdatedAt: updatedAt.millisecondsSinceEpoch,
  };
  }
  // 从Map创建Task对象
  factory Task.fromMap(Map<String, dynamic> map) {
    return Task(
    id: map[DatabaseHelper.columnId],
    title: map[DatabaseHelper.columnTitle],
    description: map[DatabaseHelper.columnDescription],
    isCompleted: map[DatabaseHelper.columnIsCompleted] == 1,
    createdAt: DateTime.fromMillisecondsSinceEpoch(
    map[DatabaseHelper.columnCreatedAt]),
    updatedAt: DateTime.fromMillisecondsSinceEpoch(
    map[DatabaseHelper.columnUpdatedAt]),
    );
    }
    
    String toString() {
    return 'Task{id: $id, title: $title, completed: $isCompleted}';
    }
    }

第四步:创建数据访问对象

class TaskDao {
final DatabaseHelper _dbHelper = DatabaseHelper();
// 1. 插入新任务
Future<int> insertTask(Task task) async {
  final db = await _dbHelper.database;
  // 更新时间戳
  task.updatedAt = DateTime.now();
  final id = await db.insert(
  DatabaseHelper.tableTasks,
  task.toMap(),
  conflictAlgorithm: ConflictAlgorithm.replace,
  );
  print('任务创建成功,ID: $id');
  return id;
  }
  // 2. 根据ID查询任务
  Future<Task?> getTaskById(int id) async {
    final db = await _dbHelper.database;
    final maps = await db.query(
    DatabaseHelper.tableTasks,
    where: '${DatabaseHelper.columnId} = ?',
    whereArgs: [id],
    );
    if (maps.isNotEmpty) {
    return Task.fromMap(maps.first);
    }
    return null;
    }
    // 3. 查询所有任务
    Future<List<Task>> getAllTasks() async {
      final db = await _dbHelper.database;
      final maps = await db.query(
      DatabaseHelper.tableTasks,
      orderBy: '${DatabaseHelper.columnCreatedAt} DESC',
      );
      return maps.map((map) => Task.fromMap(map)).toList();
      }
      // 4. 查询未完成的任务
      Future<List<Task>> getIncompleteTasks() async {
        final db = await _dbHelper.database;
        final maps = await db.query(
        DatabaseHelper.tableTasks,
        where: '${DatabaseHelper.columnIsCompleted} = ?',
        whereArgs: [0],
        orderBy: '${DatabaseHelper.columnCreatedAt} DESC',
        );
        return maps.map((map) => Task.fromMap(map)).toList();
        }
        // 5. 更新任务
        Future<int> updateTask(Task task) async {
          final db = await _dbHelper.database;
          // 更新修改时间
          task.updatedAt = DateTime.now();
          final count = await db.update(
          DatabaseHelper.tableTasks,
          task.toMap(),
          where: '${DatabaseHelper.columnId} = ?',
          whereArgs: [task.id],
          );
          if (count > 0) {
          print('任务更新成功: ${task.title}');
          }
          return count;
          }
          // 6. 删除任务
          Future<int> deleteTask(int id) async {
            final db = await _dbHelper.database;
            final count = await db.delete(
            DatabaseHelper.tableTasks,
            where: '${DatabaseHelper.columnId} = ?',
            whereArgs: [id],
            );
            if (count > 0) {
            print('任务删除成功, ID: $id');
            }
            return count;
            }
            // 7. 批量操作
            Future<void> batchInsertTasks(List<Task> tasks) async {
              final db = await _dbHelper.database;
              final batch = db.batch();
              for (final task in tasks) {
              batch.insert(DatabaseHelper.tableTasks, task.toMap());
              }
              await batch.commit();
              print('批量插入 ${tasks.length} 个任务成功');
              }
              // 8. 复杂查询:搜索任务
              Future<List<Task>> searchTasks(String keyword) async {
                final db = await _dbHelper.database;
                final maps = await db.query(
                DatabaseHelper.tableTasks,
                where: '''
                ${DatabaseHelper.columnTitle} LIKE ? OR
                ${DatabaseHelper.columnDescription} LIKE ?
                ''',
                whereArgs: ['%$keyword%', '%$keyword%'],
                orderBy: '${DatabaseHelper.columnCreatedAt} DESC',
                );
                return maps.map((map) => Task.fromMap(map)).toList();
                }
                // 9. 事务操作
                Future<void> markAllAsCompleted() async {
                  final db = await _dbHelper.database;
                  await db.transaction((txn) async {
                  await txn.update(
                  DatabaseHelper.tableTasks,
                  {
                  DatabaseHelper.columnIsCompleted: 1,
                  DatabaseHelper.columnUpdatedAt: DateTime.now().millisecondsSinceEpoch,
                  },
                  );
                  });
                  print('所有任务标记为完成');
                  }
                  }

第五步:在UI中使用

class TaskListPage extends StatefulWidget {

_TaskListPageState createState() => _TaskListPageState();
}
class _TaskListPageState extends State<TaskListPage> {
  final TaskDao _taskDao = TaskDao();
  List<Task> _tasks = [];
    bool _isLoading = true;
    
    void initState() {
    super.initState();
    _loadTasks();
    }
    Future<void> _loadTasks() async {
      setState(() => _isLoading = true);
      try {
      final tasks = await _taskDao.getAllTasks();
      setState(() => _tasks = tasks);
      } catch (e) {
      print('加载任务失败: $e');
      // 错误提示
      } finally {
      setState(() => _isLoading = false);
      }
      }
      Future<void> _addTask() async {
        final newTask = Task(
        title: '新任务 ${DateTime.now().second}',
        description: '这是一个新任务的描述',
        );
        await _taskDao.insertTask(newTask);
        await _loadTasks(); // 重新加载列表
        }
        Future<void> _toggleTaskCompletion(Task task) async {
          task.isCompleted = !task.isCompleted;
          await _taskDao.updateTask(task);
          await _loadTasks();
          }
          Future<void> _deleteTask(Task task) async {
            if (task.id != null) {
            await _taskDao.deleteTask(task.id!);
            await _loadTasks();
            }
            }
            
            Widget build(BuildContext context) {
            return Scaffold(
            appBar: AppBar(
            title: Text('任务管理器 (${_tasks.length})'),
            actions: [
            IconButton(
            icon: Icon(Icons.add),
            onPressed: _addTask,
            ),
            ],
            ),
            body: _isLoading
            ? Center(child: CircularProgressIndicator())
            : _tasks.isEmpty
            ? Center(child: Text('还没有任务,点击+号添加吧!'))
            : ListView.builder(
            itemCount: _tasks.length,
            itemBuilder: (context, index) {
            final task = _tasks[index];
            return Dismissible(
            key: Key(task.id.toString()),
            background: Container(color: Colors.red),
            onDismissed: (_) => _deleteTask(task),
            child: ListTile(
            leading: Checkbox(
            value: task.isCompleted,
            onChanged: (_) => _toggleTaskCompletion(task),
            ),
            title: Text(
            task.title,
            style: TextStyle(
            decoration: task.isCompleted
            ? TextDecoration.lineThrough
            : TextDecoration.none,
            ),
            ),
            subtitle: Text(
            task.description ?? '暂无描述',
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
            ),
            trailing: Text(
            DateFormat('MM-dd HH:mm').format(task.createdAt),
            style: TextStyle(fontSize: 12, color: Colors.grey),
            ),
            ),
            );
            },
            ),
            );
            }
            }

五、 性能优化

5.1 数据库迁移

当你的数据结构需要变更时,就需要数据库迁移:

class DatabaseHelper {
static const int _dbVersion = 2; // 版本升级
Future<void> _upgradeDatabase(Database db, int oldVersion, int newVersion) async {
  for (int version = oldVersion + 1; version <= newVersion; version++) {
  switch (version) {
  case 2:
  await _migrateToV2(db);
  break;
  case 3:
  await _migrateToV3(db);
  break;
  }
  }
  }
  Future<void> _migrateToV2(Database db) async {
    // 添加优先级字段
    await db.execute('''
    ALTER TABLE ${DatabaseHelper.tableTasks}
    ADD COLUMN priority INTEGER NOT NULL DEFAULT 0
    ''');
    print('数据库迁移到版本2成功');
    }
    Future<void> _migrateToV3(Database db) async {
      // 创建新表或更复杂的迁移
      await db.execute('''
      CREATE TABLE categories (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      color TEXT NOT NULL
      )
      ''');
      print('数据库迁移到版本3成功');
      }
      }
5.2 使用ORM简化操作

虽然直接使用SQL很强大,但ORM可以让代码更简洁。推荐 floormoor

dependencies:
floor: ^1.4.0
sqflite: ^2.0.0
5.3 优化技巧
  1. 使用索引:对经常查询的字段创建索引
  2. 批量操作:使用 batch() 进行批量插入/更新
  3. 连接池:保持数据库连接,避免频繁开关
  4. 分页查询:大数据集使用 LIMITOFFSET
  5. 避免N+1查询:使用 JOIN 一次性获取关联数据
// 分页查询
Future<List<Task>> getTasksPaginated(int page, int pageSize) async {
  final db = await _dbHelper.database;
  final offset = (page - 1) * pageSize;
  final maps = await db.query(
  DatabaseHelper.tableTasks,
  limit: pageSize,
  offset: offset,
  orderBy: '${DatabaseHelper.columnCreatedAt} DESC',
  );
  return maps.map((map) => Task.fromMap(map)).toList();
  }

六、 方案对比

下面我们通过多维度对以上几种本地存储方案进行一个详细对比:

维度shared_preferences文件存储SQLiteHive
数据类型基本类型任意数据结构化数据任意对象
查询能力键值查询顺序读取复杂SQL查询键值+条件查询
性能中等快(有索引)非常快
复杂度简单中等复杂简单
数据量小(<1MB)中等
是否需要序列化需要需要需要不需要

实际项目开发中,我们如何选择本地存储?可按下面策略进行存储方案选型:

很小 < 1MB
中等
很大
简单键值对
复杂对象
开始选型
数据量大小
数据类型
文件存储
是否需要复杂查询
shared_preferences
Hive
SQLite
完成

以上选型策略概述以下:

  1. 用户设置、登录令牌shared_preferences
  2. App配置、日志文件 → 文件存储
  3. 聊天记录、商品目录 → SQLite
  4. 缓存数据、临时状态 → Hive
  5. 需要极致性能 → Hive
  6. 需要复杂关系查询 → SQLite

七、 综合应用代码实战

下面我们构建一个完整的用户数据管理方案,综合运用多种存储方式:

class UserDataManager {
final SPManager _spManager = SPManager();
final FileManager _fileManager = FileManager();
final TaskDao _taskDao = TaskDao();
// 1. 用户登录状态 - 使用shared_preferences
Future<void> saveLoginState(User user) async {
  await _spManager.init();
  await _spManager._prefs.setString('user_id', user.id);
  await _spManager._prefs.setString('user_token', user.token);
  await _spManager._prefs.setBool('is_logged_in', true);
  // 同时保存用户信息到SQLite
  // await _userDao.insertUser(user);
  }
  // 2. 用户偏好设置 - 使用文件存储
  Future<void> saveUserPreferences(UserPreferences prefs) async {
    await _fileManager.writeJsonToFile(
    prefs.toJson(),
    'user_preferences.json'
    );
    }
    // 3. 用户任务数据 - 使用SQLite
    Future<void> syncUserTasks(List<Task> tasks) async {
      await _taskDao.batchInsertTasks(tasks);
      }
      // 4. 清理所有用户数据
      Future<void> clearAllUserData() async {
        // 清理SP
        await _spManager._prefs.clear();
        // 清理配置文件
        await _fileManager.deleteFile('user_preferences.json');
        // 清理数据库
        // await _taskDao.deleteAllTasks();
        }
        }

写在最后

至此本地数据存储的知识就全学完了,记住这几个核心要点:

  1. 性能意识:大数据量时考虑索引、分页、批量操作
  2. 错误处理:存储操作可能失败,一定要有完善的错误处理
  3. 数据安全:敏感信息考虑加密存储
  4. 测试验证:数据库迁移等复杂操作要充分测试

本地存储是App开发的基础,掌握好它,就能开发出体验流畅、数据可靠的应用。希望这篇文章能帮助到你!
我们下期再见!

posted @ 2025-12-16 09:33  clnchanpin  阅读(58)  评论(0)    收藏  举报