← Frontend / Flutter

12_本地存储

Flutter 本地数据存储方案

SharedPreferences(轻量键值)、SQLite(结构化数据)、文件存储。


三种方案对比

方案 类比 Web 适用场景
SharedPreferences localStorage 简单配置、Token、用户偏好
SQLite(sqflite) IndexedDB 复杂结构化数据、离线数据库
文件存储 File System API 大文件、图片、日志

SharedPreferences

dependencies:
  shared_preferences: ^2.2.0
import 'package:shared_preferences/shared_preferences.dart';

class PrefsService {
  static SharedPreferences? _prefs;

  // 初始化(在 main() 中调用一次)
  static Future<void> init() async {
    _prefs = await SharedPreferences.getInstance();
  }

  // 写入
  static Future<void> setString(String key, String value) =>
      _prefs!.setString(key, value);
  static Future<void> setBool(String key, bool value) =>
      _prefs!.setBool(key, value);
  static Future<void> setInt(String key, int value) =>
      _prefs!.setInt(key, value);
  static Future<void> setStringList(String key, List<String> value) =>
      _prefs!.setStringList(key, value);

  // 读取(带默认值)
  static String getString(String key, {String defaultValue = ''}) =>
      _prefs!.getString(key) ?? defaultValue;
  static bool getBool(String key, {bool defaultValue = false}) =>
      _prefs!.getBool(key) ?? defaultValue;
  static int getInt(String key, {int defaultValue = 0}) =>
      _prefs!.getInt(key) ?? defaultValue;

  // 删除 / 清空
  static Future<void> remove(String key) => _prefs!.remove(key);
  static Future<void> clear() => _prefs!.clear();
  static bool hasKey(String key) => _prefs!.containsKey(key);
}

// main.dart 中初始化
void main() async {
  WidgetsFlutterBinding.ensureInitialized(); // 异步初始化前必须调用
  await PrefsService.init();
  runApp(const MyApp());
}

// 使用
await PrefsService.setString('auth_token', token);
final token = PrefsService.getString('auth_token');

await PrefsService.setBool('is_dark_mode', true);
final isDark = PrefsService.getBool('is_dark_mode');

SQLite(sqflite)

dependencies:
  sqflite: ^2.3.0
  path: ^1.8.0
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

class DatabaseHelper {
  static Database? _db;

  static Future<Database> get db async {
    _db ??= await _init();
    return _db!;
  }

  static Future<Database> _init() async {
    final path = join(await getDatabasesPath(), 'app.db');
    return openDatabase(
      path,
      version: 2,
      onCreate: (db, version) async {
        await db.execute('''
          CREATE TABLE users (
            id    INTEGER PRIMARY KEY AUTOINCREMENT,
            name  TEXT    NOT NULL,
            email TEXT    UNIQUE NOT NULL,
            age   INTEGER DEFAULT 0,
            created_at TEXT DEFAULT CURRENT_TIMESTAMP
          )
        ''');
      },
      onUpgrade: (db, oldVersion, newVersion) async {
        // 数据库版本升级(类似 migration)
        if (oldVersion < 2) {
          await db.execute('ALTER TABLE users ADD COLUMN avatar TEXT');
        }
      },
    );
  }

  // ── CRUD ────────────────────────────────

  // 插入(返回新行的 id)
  static Future<int> insertUser(Map<String, dynamic> user) async {
    final database = await db;
    return database.insert(
      'users',
      user,
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }

  // 查询全部
  static Future<List<Map<String, dynamic>>> getUsers() async {
    final database = await db;
    return database.query('users', orderBy: 'created_at DESC');
  }

  // 条件查询
  static Future<Map<String, dynamic>?> getUserById(int id) async {
    final database = await db;
    final rows = await database.query(
      'users',
      where: 'id = ?',
      whereArgs: [id],
      limit: 1,
    );
    return rows.isEmpty ? null : rows.first;
  }

  // 模糊搜索
  static Future<List<Map<String, dynamic>>> searchUsers(String keyword) async {
    final database = await db;
    return database.query(
      'users',
      where: 'name LIKE ? OR email LIKE ?',
      whereArgs: ['%$keyword%', '%$keyword%'],
    );
  }

  // 更新
  static Future<int> updateUser(int id, Map<String, dynamic> data) async {
    final database = await db;
    return database.update('users', data, where: 'id = ?', whereArgs: [id]);
  }

  // 删除
  static Future<int> deleteUser(int id) async {
    final database = await db;
    return database.delete('users', where: 'id = ?', whereArgs: [id]);
  }

  // 原始 SQL
  static Future<List<Map<String, dynamic>>> rawQuery(String sql, [List? args]) async {
    final database = await db;
    return database.rawQuery(sql, args);
  }
}

// 使用示例
final id = await DatabaseHelper.insertUser({
  'name': 'Alice',
  'email': 'alice@example.com',
  'age': 25,
});

final users = await DatabaseHelper.getUsers();
for (final user in users) {
  print('${user['id']}: ${user['name']}');
}

文件存储

dependencies:
  path_provider: ^2.1.0
import 'dart:io';
import 'dart:convert';
import 'package:path_provider/path_provider.dart';

class FileService {
  // 应用私有文档目录(用户不可见,卸载时清除)
  static Future<Directory> get _docDir =>
      getApplicationDocumentsDirectory();

  // 临时缓存目录(系统可能自动清理)
  static Future<Directory> get _cacheDir =>
      getTemporaryDirectory();

  // 写入文本
  static Future<void> writeText(String filename, String content) async {
    final dir = await _docDir;
    await File('${dir.path}/$filename').writeAsString(content);
  }

  // 读取文本
  static Future<String> readText(String filename) async {
    final dir = await _docDir;
    final file = File('${dir.path}/$filename');
    if (!await file.exists()) return '';
    return file.readAsString();
  }

  // 写入 JSON
  static Future<void> writeJson(String filename, dynamic data) async {
    await writeText(filename, jsonEncode(data));
  }

  // 读取 JSON
  static Future<dynamic> readJson(String filename) async {
    final text = await readText(filename);
    if (text.isEmpty) return null;
    return jsonDecode(text);
  }

  // 检查文件是否存在
  static Future<bool> exists(String filename) async {
    final dir = await _docDir;
    return File('${dir.path}/$filename').exists();
  }

  // 删除文件
  static Future<void> delete(String filename) async {
    final dir = await _docDir;
    final file = File('${dir.path}/$filename');
    if (await file.exists()) await file.delete();
  }

  // 列出所有文件
  static Future<List<String>> listFiles() async {
    final dir = await _docDir;
    final files = dir.listSync();
    return files.map((f) => f.path.split('/').last).toList();
  }
}

// 使用
await FileService.writeJson('user_cache.json', {'name': 'Alice', 'age': 25});
final data = await FileService.readJson('user_cache.json');