flutter学习第 13 节:本地存储

在移动应用开发中,本地存储是一项关键功能,用于保存用户偏好设置、离线数据、登录状态等信息。Flutter 提供了多种本地存储方案,适用于不同的场景需求。本节课将介绍 Flutter 中常用的本地存储方式,包括轻量级的键值对存储和更复杂的数据库存储,并通过实例展示其实际应用。

一、本地存储概述

本地存储在移动应用中具有重要作用,主要应用场景包括:

  • 保存用户登录状态,避免重复登录
  • 存储应用配置和用户偏好设置
  • 缓存网络请求数据,实现离线功能
  • 存储结构化数据,如聊天记录、任务列表等

Flutter 中常用的本地存储方案有:

  1. shared_preferences:轻量级键值对存储,适用于简单数据
  2. sqflite:SQLite 数据库封装,适用于结构化数据
  3. hive:高性能 NoSQL 数据库,纯 Dart 实现
  4. flutter_secure_storage:安全存储,适用于敏感信息如令牌

本节课重点介绍前两种最常用的存储方案。


二、轻量级存储:shared_preferences

shared_preferences 是 Flutter 社区提供的一个插件,用于存储简单的键值对数据。它在 iOS 上使用 NSUserDefaults,在 Android 上使用 SharedPreferences,提供了跨平台的一致 API。

1. 安装与配置

在 pubspec.yaml 中添加依赖:

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  shared_preferences: ^2.5.3  # 使用最新版本

运行 flutter pub get 安装依赖。

2. 基本使用方法

shared_preferences 支持存储的数据类型包括:String、int、double、bool 和 List<String>。

基本操作步骤:

  1. 获取 SharedPreferences 实例
  2. 使用相应的方法进行数据读写
  3. 无需手动关闭实例
dart 复制代码
import 'package:shared_preferences/shared_preferences.dart';

// 保存数据
Future<void> saveData() async {
  // 获取实例
  final prefs = await SharedPreferences.getInstance();

  // 存储不同类型的数据
  await prefs.setString('username', 'john_doe');
  await prefs.setInt('age', 30);
  await prefs.setDouble('height', 1.75);
  await prefs.setBool('isPremium', false);
  await prefs.setStringList('hobbies', ['reading', 'sports', 'coding']);
}

// 读取数据
Future<void> readData() async {
  final prefs = await SharedPreferences.getInstance();

  // 读取数据,提供默认值
  String username = prefs.getString('username') ?? 'Guest';
  int age = prefs.getInt('age') ?? 0;
  double height = prefs.getDouble('height') ?? 0.0;
  bool isPremium = prefs.getBool('isPremium') ?? false;
  List<String> hobbies = prefs.getStringList('hobbies') ?? [];

  print('Username: $username');
  print('Age: $age');
  print('Height: $height');
  print('Is Premium: $isPremium');
  print('Hobbies: $hobbies');
}

// 更新数据
Future<void> updateData() async {
  final prefs = await SharedPreferences.getInstance();
  // 更新已存在的键的值
  await prefs.setInt('age', 31);
  await prefs.setBool('isPremium', true);
}

// 删除数据
Future<void> deleteData() async {
  final prefs = await SharedPreferences.getInstance();
  // 删除指定键
  await prefs.remove('height');

  // 清除所有数据
  // await prefs.clear();
}

3. shared_preferences 封装

为了简化使用并避免重复代码,可以封装一个工具类:

dart 复制代码
import 'package:shared_preferences/shared_preferences.dart';

class PrefsService {
  // 单例模式
  static final PrefsService _instance = PrefsService._internal();
  factory PrefsService() => _instance;
  PrefsService._internal();

  late SharedPreferences _prefs;

  // 初始化
  Future<void> init() async {
    _prefs = await SharedPreferences.getInstance();
  }

  // 存储方法
  Future<void> setString(String key, String value) async {
    await _prefs.setString(key, value);
  }

  Future<void> setInt(String key, int value) async {
    await _prefs.setInt(key, value);
  }

  Future<void> setDouble(String key, double value) async {
    await _prefs.setDouble(key, value);
  }

  Future<void> setBool(String key, bool value) async {
    await _prefs.setBool(key, value);
  }

  Future<void> setStringList(String key, List<String> value) async {
    await _prefs.setStringList(key, value);
  }

  // 读取方法
  String getString(String key, {String defaultValue = ''}) {
    return _prefs.getString(key) ?? defaultValue;
  }

  int getInt(String key, {int defaultValue = 0}) {
    return _prefs.getInt(key) ?? defaultValue;
  }

  double getDouble(String key, {double defaultValue = 0.0}) {
    return _prefs.getDouble(key) ?? defaultValue;
  }

  bool getBool(String key, {bool defaultValue = false}) {
    return _prefs.getBool(key) ?? defaultValue;
  }

  List<String> getStringList(String key, {List<String> defaultValue = const []}) {
    return _prefs.getStringList(key) ?? defaultValue;
  }

  // 删除方法
  Future<void> remove(String key) async {
    await _prefs.remove(key);
  }

  Future<void> clear() async {
    await _prefs.clear();
  }

  // 检查键是否存在
  bool containsKey(String key) {
    return _prefs.containsKey(key);
  }
}

使用封装类:

dart 复制代码
// 初始化(通常在 app 启动时)
await PrefsService().init();

// 存储数据
await PrefsService().setString('username', 'jane_smith');
await PrefsService().setInt('score', 100);

// 读取数据
String username = PrefsService().getString('username');
int score = PrefsService().getInt('score');

4. shared_preferences 适用场景与局限性

适用场景:

  • 存储用户偏好设置(如主题模式、语言选择)
  • 保存简单的用户状态(如登录状态标记)
  • 存储少量的配置信息

局限性:

  • 不适合存储大量数据
  • 不支持复杂数据结构
  • 数据存储在明文文件中,不适合存储敏感信息
  • 没有查询功能,只能通过键获取值

三、数据库存储:sqflite 基础

对于需要存储大量结构化数据的场景,sqflite 是更好的选择。sqflite 是 Flutter 中 SQLite 数据库的封装,提供了完整的 SQL 操作能力。

1. 安装与配置

在 pubspec.yaml 中添加依赖:

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  sqflite: ^2.4.2  # SQLite 数据库
  path: ^1.9.1     # 用于处理文件路径

运行 flutter pub get 安装依赖。

2. 数据库基本操作

SQLite 数据库操作主要包括:创建数据库和表、插入数据、查询数据、更新数据和删除数据。

创建数据库和表

dart 复制代码
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

class DatabaseHelper {
  // 数据库名称
  static const _databaseName = "MyDatabase.db";
  // 数据库版本
  static const _databaseVersion = 1;

  // 表名
  static const table = 'notes';

  // 列名
  static const columnId = '_id';
  static const columnTitle = 'title';
  static const columnContent = 'content';
  static const columnCreatedAt = 'created_at';

  // 单例模式
  DatabaseHelper._privateConstructor();
  static final DatabaseHelper instance = DatabaseHelper._privateConstructor();

  // 数据库实例
  static Database? _database;
  Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDatabase();
    return _database!;
  }

  // 初始化数据库
  _initDatabase() async {
    String path = join(await getDatabasesPath(), _databaseName);
    return await openDatabase(path,
        version: _databaseVersion,
        onCreate: _onCreate);
  }

  // 创建表
  Future _onCreate(Database db, int version) async {
    await db.execute('''
          CREATE TABLE $table (
            $columnId INTEGER PRIMARY KEY AUTOINCREMENT,
            $columnTitle TEXT NOT NULL,
            $columnContent TEXT,
            $columnCreatedAt TEXT NOT NULL
          )
          ''');
  }
}

插入数据

dart 复制代码
// 插入数据
Future<int> insertNote(Map<String, dynamic> note) async {
  Database db = await DatabaseHelper.instance.database;
  // 插入数据并返回新记录的 ID
  return await db.insert(DatabaseHelper.table, note);
}

// 使用示例
void addNote() async {
  Map<String, dynamic> newNote = {
    DatabaseHelper.columnTitle: 'First Note',
    DatabaseHelper.columnContent: 'This is my first note',
    DatabaseHelper.columnCreatedAt: DateTime.now().toIso8601String()
  };

  int id = await insertNote(newNote);
  print('Inserted note with id: $id');
}

查询数据

dart 复制代码
// 查询所有数据
Future<List<Map<String, dynamic>>> queryAllNotes() async {
  Database db = await DatabaseHelper.instance.database;
  return await db.query(DatabaseHelper.table);
}

// 根据 ID 查询
Future<List<Map<String, dynamic>>> queryNote(int id) async {
  Database db = await DatabaseHelper.instance.database;
  return await db.query(DatabaseHelper.table,
      where: '${DatabaseHelper.columnId} = ?',
      whereArgs: [id]);
}

// 使用示例
void getNotes() async {
  List<Map<String, dynamic>> notes = await queryAllNotes();
  print('All notes: $notes');

  if (notes.isNotEmpty) {
    List<Map<String, dynamic>> singleNote = await queryNote(notes[0][DatabaseHelper.columnId]);
    print('First note: $singleNote');
  }
}

更新数据

dart 复制代码
// 更新数据
Future<int> updateNote(Map<String, dynamic> note) async {
  Database db = await DatabaseHelper.instance.database;
  int id = note[DatabaseHelper.columnId];
  // 返回受影响的行数
  return await db.update(DatabaseHelper.table, note,
      where: '${DatabaseHelper.columnId} = ?',
      whereArgs: [id]);
}

// 使用示例
void modifyNote(int noteId) async {
  Map<String, dynamic> updatedNote = {
    DatabaseHelper.columnId: noteId,
    DatabaseHelper.columnTitle: 'Updated Note',
    DatabaseHelper.columnContent: 'This note has been updated',
    DatabaseHelper.columnCreatedAt: DateTime.now().toIso8601String()
  };

  int rowsAffected = await updateNote(updatedNote);
  print('Updated $rowsAffected rows');
}

删除数据

dart 复制代码
// 删除数据
Future<int> deleteNote(int id) async {
  Database db = await DatabaseHelper.instance.database;
  // 返回受影响的行数
  return await db.delete(DatabaseHelper.table,
      where: '${DatabaseHelper.columnId} = ?',
      whereArgs: [id]);
}

// 使用示例
void removeNote(int noteId) async {
  int rowsDeleted = await deleteNote(noteId);
  print('Deleted $rowsDeleted rows');
}

3. 数据库版本迁移

当应用升级需要修改数据库结构时,需要进行数据库迁移:

dart 复制代码
// 在 DatabaseHelper 类中修改 _initDatabase 方法
_initDatabase() async {
  String path = join(await getDatabasesPath(), _databaseName);
  return await openDatabase(
    path,
    version: _databaseVersion,
    onCreate: _onCreate,
    onUpgrade: _onUpgrade, // 添加升级回调
    onDowngrade: onDatabaseDowngradeDelete, // 降级时删除数据库
  );
}

// 数据库升级
Future _onUpgrade(Database db, int oldVersion, int newVersion) async {
  if (oldVersion < 2) {
    // 版本 2 新增了一个 'category' 列
    await db.execute('''
      ALTER TABLE ${DatabaseHelper.table} 
      ADD COLUMN category TEXT
    ''');
  }
  if (oldVersion < 3) {
    // 版本 3 新增了一个 'priority' 列
    await db.execute('''
      ALTER TABLE ${DatabaseHelper.table} 
      ADD COLUMN priority INTEGER DEFAULT 0
    ''');
  }
}

4. 模型类与数据库操作封装

为了更好地管理数据,通常会创建模型类并封装数据库操作:

dart 复制代码
// 笔记模型类
class Note {
  final int? id;
  final String title;
  final String? content;
  final String createdAt;
  final String? category;
  final int priority;

  Note({
    this.id,
    required this.title,
    this.content,
    required this.createdAt,
    this.category,
    this.priority = 0,
  });

  // 从 Map 转换为 Note 对象
  factory Note.fromMap(Map<String, dynamic> map) {
    return Note(
      id: map[DatabaseHelper.columnId],
      title: map[DatabaseHelper.columnTitle],
      content: map[DatabaseHelper.columnContent],
      createdAt: map[DatabaseHelper.columnCreatedAt],
      category: map['category'],
      priority: map['priority'] ?? 0,
    );
  }

  // 转换为 Map
  Map<String, dynamic> toMap() {
    return {
      DatabaseHelper.columnId: id,
      DatabaseHelper.columnTitle: title,
      DatabaseHelper.columnContent: content,
      DatabaseHelper.columnCreatedAt: createdAt,
      'category': category,
      'priority': priority,
    };
  }
}

// 封装数据库操作
class NoteService {
  // 插入笔记
  Future<int> insertNote(Note note) async {
    Database db = await DatabaseHelper.instance.database;
    return await db.insert(DatabaseHelper.table, note.toMap());
  }

  // 获取所有笔记
  Future<List<Note>> getAllNotes() async {
    Database db = await DatabaseHelper.instance.database;
    List<Map<String, dynamic>> maps = await db.query(DatabaseHelper.table);
    return List.generate(maps.length, (i) => Note.fromMap(maps[i]));
  }

  // 根据 ID 获取笔记
  Future<Note?> getNoteById(int id) async {
    Database db = await DatabaseHelper.instance.database;
    List<Map<String, dynamic>> maps = await db.query(
      DatabaseHelper.table,
      where: '${DatabaseHelper.columnId} = ?',
      whereArgs: [id],
    );
    if (maps.isNotEmpty) {
      return Note.fromMap(maps.first);
    }
    return null;
  }

  // 更新笔记
  Future<int> updateNote(Note note) async {
    Database db = await DatabaseHelper.instance.database;
    return await db.update(
      DatabaseHelper.table,
      note.toMap(),
      where: '${DatabaseHelper.columnId} = ?',
      whereArgs: [note.id],
    );
  }

  // 删除笔记
  Future<int> deleteNote(int id) async {
    Database db = await DatabaseHelper.instance.database;
    return await db.delete(
      DatabaseHelper.table,
      where: '${DatabaseHelper.columnId} = ?',
      whereArgs: [id],
    );
  }

  // 按类别查询笔记
  Future<List<Note>> getNotesByCategory(String category) async {
    Database db = await DatabaseHelper.instance.database;
    List<Map<String, dynamic>> maps = await db.query(
      DatabaseHelper.table,
      where: 'category = ?',
      whereArgs: [category],
    );
    return List.generate(maps.length, (i) => Note.fromMap(maps[i]));
  }

  // 按优先级排序查询
  Future<List<Note>> getNotesSortedByPriority() async {
    Database db = await DatabaseHelper.instance.database;
    List<Map<String, dynamic>> maps = await db.query(
      DatabaseHelper.table,
      orderBy: 'priority DESC',
    );
    return List.generate(maps.length, (i) => Note.fromMap(maps[i]));
  }
}

四、实例:保存用户登录状态

下面实现一个保存用户登录状态的功能,使用 shared_preferences 存储登录信息:

dart 复制代码
import 'package:shared_preferences/shared_preferences.dart';

class AuthService {
  static const _tokenKey = 'auth_token';
  static const _userIdKey = 'user_id';
  static const _usernameKey = 'username';

  // 保存登录状态
  Future<void> saveLoginState({
    required String token,
    required String userId,
    required String username,
  }) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString(_tokenKey, token);
    await prefs.setString(_userIdKey, userId);
    await prefs.setString(_usernameKey, username);
  }

  // 获取当前登录用户信息
  Future<Map<String, String>?> getCurrentUser() async {
    final prefs = await SharedPreferences.getInstance();
    final token = prefs.getString(_tokenKey);
    final userId = prefs.getString(_userIdKey);
    final username = prefs.getString(_usernameKey);

    if (token != null && userId != null && username != null) {
      return {
        'token': token,
        'userId': userId,
        'username': username,
      };
    }
    return null;
  }

  // 检查是否已登录
  Future<bool> isLoggedIn() async {
    final user = await getCurrentUser();
    return user != null;
  }

  // 登出,清除登录状态
  Future<void> logout() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.remove(_tokenKey);
    await prefs.remove(_userIdKey);
    await prefs.remove(_usernameKey);
  }

  // 获取认证令牌
  Future<String?> getAuthToken() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getString(_tokenKey);
  }
}

在应用启动时检查登录状态:

dart 复制代码
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 检查登录状态
  AuthService authService = AuthService();
  bool isLoggedIn = await authService.isLoggedIn();
  
  runApp(MyApp(isLoggedIn: isLoggedIn));
}

class MyApp extends StatelessWidget {
  final bool isLoggedIn;

  const MyApp({super.key, required this.isLoggedIn});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Local Storage Demo',
      home: isLoggedIn ? const HomeScreen() : const LoginScreen(),
    );
  }
}

登录页面实现:

dart 复制代码
class LoginScreen extends StatefulWidget {
  const LoginScreen({super.key});

  @override
  State<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final _usernameController = TextEditingController();
  final _passwordController = TextEditingController();
  final _authService = AuthService();
  bool _isLoading = false;

  Future<void> _login(BuildContext context) async {
    setState(() {
      _isLoading = true;
    });

    // 模拟 API 登录请求
    await Future.delayed(const Duration(seconds: 2));

    // 实际应用中应该验证用户名和密码
    if (_usernameController.text.isNotEmpty &&
        _passwordController.text.isNotEmpty) {
      // 保存登录状态
      await _authService.saveLoginState(
        token: 'fake_auth_token_123456',
        userId: 'user_123',
        username: _usernameController.text,
      );

      // 导航到主页
      if (mounted) {
        Navigator.pushReplacement(
          context,
          MaterialPageRoute(builder: (context) => const HomeScreen()),
        );
      }
    } else {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Please enter username and password')),
        );
      }
    }

    setState(() {
      _isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Login')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: _usernameController,
              decoration: const InputDecoration(labelText: 'Username'),
            ),
            TextField(
              controller: _passwordController,
              decoration: const InputDecoration(labelText: 'Password'),
              obscureText: true,
            ),
            const SizedBox(height: 20),
            _isLoading
                ? const CircularProgressIndicator()
                : ElevatedButton(
                    onPressed: () => _login(context),
                    child: const Text('Login'),
                  ),
          ],
        ),
      ),
    );
  }
}

主页实现:

dart 复制代码
class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final _authService = AuthService();
  String? _username;

  @override
  void initState() {
    super.initState();
    _loadUserInfo();
  }

  Future<void> _loadUserInfo() async {
    final user = await _authService.getCurrentUser();
    setState(() {
      _username = user?['username'];
    });
  }

  Future<void> _logout(BuildContext context) async {
    await _authService.logout();
    if (mounted) {
      Navigator.pushReplacement(
        context,
        MaterialPageRoute(builder: (context) => const LoginScreen()),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        actions: [
          IconButton(
            icon: const Icon(Icons.logout),
            onPressed: () => _logout(context),
          ),
        ],
      ),
      body: Center(
        child: _username != null
            ? Text('Welcome, $_username! You are logged in.')
            : const Text('Loading user info...'),
      ),
    );
  }
}

五、实例:离线数据缓存

使用 sqflite 实现网络数据的本地缓存,实现离线查看功能:

dart 复制代码
// 新闻模型类
class Article {
  final String id;
  final String title;
  final String? description;
  final String url;
  final String? urlToImage;
  final String publishedAt;
  final String? content;

  Article({
    required this.id,
    required this.title,
    this.description,
    required this.url,
    this.urlToImage,
    required this.publishedAt,
    this.content,
  });

  // 从 Map 转换
  factory Article.fromMap(Map<String, dynamic> map) {
    return Article(
      id: map['id'],
      title: map['title'],
      description: map['description'],
      url: map['url'],
      urlToImage: map['urlToImage'],
      publishedAt: map['publishedAt'],
      content: map['content'],
    );
  }

  // 转换为 Map
  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'title': title,
      'description': description,
      'url': url,
      'urlToImage': urlToImage,
      'publishedAt': publishedAt,
      'content': content,
      'cachedAt': DateTime.now().toIso8601String(), // 缓存时间
    };
  }
}

// 新闻数据库帮助类
class NewsDatabaseHelper {
  static const _databaseName = "NewsDatabase.db";
  static const _databaseVersion = 1;
  static const table = 'articles';

  NewsDatabaseHelper._privateConstructor();
  static final NewsDatabaseHelper instance =
      NewsDatabaseHelper._privateConstructor();

  static Database? _database;
  Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDatabase();
    return _database!;
  }

  _initDatabase() async {
    String path = join(await getDatabasesPath(), _databaseName);
    return await openDatabase(
      path,
      version: _databaseVersion,
      onCreate: _onCreate,
    );
  }

  Future _onCreate(Database db, int version) async {
    await db.execute('''
          CREATE TABLE $table (
            id TEXT PRIMARY KEY,
            title TEXT NOT NULL,
            description TEXT,
            url TEXT NOT NULL,
            urlToImage TEXT,
            publishedAt TEXT NOT NULL,
            content TEXT,
            cachedAt TEXT NOT NULL
          )
          ''');
  }

  // 批量插入或更新文章
  Future<void> batchInsertOrUpdateArticles(List<Article> articles) async {
    Database db = await instance.database;
    Batch batch = db.batch();

    for (var article in articles) {
      batch.insert(
        table,
        article.toMap(),
        conflictAlgorithm: ConflictAlgorithm.replace, // 存在则替换
      );
    }

    await batch.commit();
  }

  // 获取所有缓存文章
  Future<List<Article>> getCachedArticles() async {
    Database db = await instance.database;
    List<Map<String, dynamic>> maps = await db.query(
      table,
      orderBy: 'publishedAt DESC',
    );
    return List.generate(maps.length, (i) => Article.fromMap(maps[i]));
  }

  // 获取单篇文章
  Future<Article?> getArticleById(String id) async {
    Database db = await instance.database;
    List<Map<String, dynamic>> maps = await db.query(
      table,
      where: 'id = ?',
      whereArgs: [id],
    );
    if (maps.isNotEmpty) {
      return Article.fromMap(maps.first);
    }
    return null;
  }

  // 清除过期缓存(例如超过7天的)
  Future<void> clearExpiredCache() async {
    Database db = await instance.database;
    final sevenDaysAgo = DateTime.now()
        .subtract(const Duration(days: 7))
        .toIso8601String();
    await db.delete(table, where: 'cachedAt < ?', whereArgs: [sevenDaysAgo]);
  }

  // 清除所有缓存
  Future<void> clearAllCache() async {
    Database db = await instance.database;
    await db.delete(table);
  }
}

新闻仓库类,整合网络请求和本地缓存:

dart 复制代码
import 'package:dio/dio.dart';

class NewsRepository {
  final Dio _dio = Dio();
  final NewsDatabaseHelper _dbHelper = NewsDatabaseHelper.instance;
  final String _apiKey = 'your_news_api_key';

  // 获取头条新闻,优先从网络获取,失败则使用缓存
  Future<List<Article>> getTopHeadlines({bool forceRefresh = false}) async {
    try {
      // 如果不是强制刷新,先检查缓存
      if (!forceRefresh) {
        final cachedArticles = await _dbHelper.getCachedArticles();
        if (cachedArticles.isNotEmpty) {
          return cachedArticles;
        }
      }

      // 网络请求
      Response response = await _dio.get(
        'https://newsapi.org/v2/top-headlines',
        queryParameters: {
          'country': 'us',
          'apiKey': _apiKey,
        },
      );

      // 解析数据
      List<Article> articles = (response.data['articles'] as List)
          .map((item) => Article(
        id: item['url'], // 使用 url 作为唯一标识
        title: item['title'],
        description: item['description'],
        url: item['url'],
        urlToImage: item['urlToImage'],
        publishedAt: item['publishedAt'],
        content: item['content'],
      ))
          .toList();

      // 缓存到本地数据库
      await _dbHelper.batchInsertOrUpdateArticles(articles);

      // 清除过期缓存
      await _dbHelper.clearExpiredCache();

      return articles;
    } catch (e) {
      // 网络请求失败,尝试返回缓存
      final cachedArticles = await _dbHelper.getCachedArticles();
      if (cachedArticles.isNotEmpty) {
        return cachedArticles;
      }
      // 没有缓存,抛出错误
      throw Exception('Failed to fetch news and no cache available');
    }
  }
}

六、其他存储方案简介

1. flutter_secure_storage

用于存储敏感信息,如令牌、密码等,数据会被加密存储:

yaml 复制代码
dependencies:
  flutter_secure_storage: ^9.2.4

使用示例:

dart 复制代码
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

final storage = FlutterSecureStorage();

// 存储敏感数据
await storage.write(key: 'auth_token', value: 'sensitive_token_123');

// 读取数据
String? token = await storage.read(key: 'auth_token');

// 删除数据
await storage.delete(key: 'auth_token');

2. Hive

Hive 是一个高性能、轻量级的 NoSQL 数据库,纯 Dart 实现,速度快且易于使用:

yaml 复制代码
dependencies:
  hive: ^2.2.3
  hive_flutter: ^1.1.0

使用示例:

dart 复制代码
// 初始化
await Hive.initFlutter();
Hive.registerAdapter(NoteAdapter()); // 注册适配器
await Hive.openBox<Note>('notes');

// 获取盒子
var box = Hive.box<Note>('notes');

// 添加数据
var note = Note(id: 1, title: 'Hive Demo', content: 'Hello Hive');
await box.put(note.id, note);

// 获取数据
Note? savedNote = box.get(1);

// 查询所有数据
List<Note> allNotes = box.values.toList();

// 更新数据
note.title = 'Updated Title';
await box.put(note.id, note);

// 删除数据
await box.delete(1);

七、本地存储最佳实践

  1. 选择合适的存储方案 :
    • 简单键值对数据使用 shared_preferences
    • 敏感信息使用 flutter_secure_storage
    • 结构化数据使用 sqflite 或 hive
  2. 数据分层管理 :
    • 封装存储操作,与业务逻辑分离
    • 使用仓库模式(Repository Pattern)整合网络和本地存储
  3. 性能优化 :
    • 批量操作数据库,减少 IO 次数
    • 及时清理过期缓存,避免存储空间过大
    • 数据库操作放在后台线程,避免阻塞 UI
  4. 错误处理 :
    • 对所有存储操作添加异常捕获
    • 网络请求失败时,提供使用缓存数据的降级策略
  5. 数据迁移 :
    • 规划好数据库版本,做好升级迁移方案
    • 重大更新时考虑数据备份和恢复机制
  6. 安全考虑 :
    • 敏感数据必须加密存储
    • 避免存储不必要的用户隐私数据
相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
茶底世界之下3 天前
为什么预览、录制、离线导出不能共享同一个背压策略?
ios·swift
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas