iT邦幫忙

2025 iThome 鐵人賽

DAY 14
1

昨天我們安裝並初始化了Hive,定義了資料的「Box」,還教會了Hive如何去認識我們的Insight模型。今天將扮演工程師的角色,把Riverpod的狀態管理系統和Hive的本地儲存系統這兩條「管道」連接起來。

將修改Provider,讓它在App啟動時從Hive讀取資料,在使用者操作時將資料寫回Hive。今天App將徹底告別「失憶症」,無論是重啟App還是處於離線狀態,使用者的茲料都將安全無虞。

第一步:讓「我的收藏」擁有永久記憶

我們先從最直接、最有成就感的「收藏」功能開始。改造 FavoriteNotifier,讓它從記憶體模式切換到硬碟模式。

打開lib/features/favorite/favorite_provider.dart,我們將進行三處修改:

  1. 從Hive讀取初始狀態:修改FavoriteNotifier的建構函式,讓它的初始狀態不再是空列表 [],而是直接從 'favorites' box中讀取所有已儲存的資料。
  2. 新增收藏時寫入Hive:修改 addFavorite 方法,在更新記憶體狀態的同時,也將新的Insight物件寫入 Hive box。
  3. 移除收藏時刪除Hive資料:修改removeFavorite方法,同樣在更新記憶體狀態後,從Hive box中刪除對應的資料。
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hive_flutter/hive_flutter.dart'; // 1. 引入 hive_flutter
import 'package:three_inspiration/data/models/insight_model.dart';

class FavoriteNotifier extends StateNotifier<List<Insight>> {
  // 獲取 Hive Box 的實例
  final Box<Insight> _box = Hive.box<Insight>('favorites');

  FavoriteNotifier() : super([]) {
    // 2. 在建構時,直接從 Box 讀取所有值來初始化 state
    state = _box.values.toList();
  }

  void addFavorite(Insight insight) {
    if (!state.any((item) => item.id == insight.id)) {
      // 3. 在更新 state 的同時,使用 put 方法寫入 Hive
      // 我們使用 insight 的 id 作為 key,確保唯一性
      _box.put(insight.id, insight);
      state = [...state, insight];
    }
  }

  void removeFavorite(String insightId) {
    // 4. 在更新 state 的同時,使用 delete 方法從 Hive 移除
    _box.delete(insightId);
    state = state.where((insight) => insight.id != insightId).toList();
  }
}

final favoriteProvider = StateNotifierProvider<FavoriteNotifier, List<Insight>>((ref) {
  return FavoriteNotifier();
});

立即驗證!

現在,重新編譯並執行你的App。去收藏幾則靈感,然後徹底關閉 App(從系統後台滑掉),再重新打開它。會發現——你收藏的那些靈感,依然靜靜地躺在收藏頁面裡~它們還在!

第二步:快取每日靈感,實現離線瀏覽

搞定了收藏列表,接下來我們來處理更複雜一些的每日靈感快取。我們希望達到的效果是:

  • App啟動時,立即從快取顯示舊資料,讓使用者不用白白等待。
  • 同時,在背景請求API獲取最新資料。
  • 如果成功獲取,就更新快取和UI。
  • 如果網路斷線,則繼續使用快取資料,App 依然可用。

這就是經典的 "Cache then Network" (先快取,後網路) 策略。讓我們來改造 InsightNotifier。

打開lib/features/insight/insight_provider.dart:

import 'package:flutter/foundation.dart'; // 用於 debugPrint
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:three_inspiration/data/models/insight_model.dart';

class InsightNotifier extends StateNotifier<List<Insight>> {
  final Box<Insight> _box = Hive.box<Insight>('insights_cache');

  InsightNotifier() : super([]) {
    // 1. 立即從快取加載資料,讓 UI 快速顯示
    state = _box.values.toList();
    debugPrint('從 Hive 快取中加載了 ${state.length} 則靈感。');
    
    // 2. 在背景執行網路請求
    _fetchFromApi();
  }

  Future<void> _fetchFromApi() async {
    try {
      // 模擬網路請求,未來會換成真實的 Repository
      debugPrint('正在從 API 獲取新靈感...');
      await Future.delayed(const Duration(seconds: 2)); // 模擬網路延遲
      final freshInsights = const [
        Insight(id: '1', summary: '靈感一:[來自網路] 文章驅動開發是一種絕佳實踐。', tags: ['開發流程', '敏捷']),
        Insight(id: '2', summary: '靈感二:[來自網路] 在設計 UI 時,給予足夠的「留白」。', tags: ['UI/UX', '設計原則']),
        Insight(id: '3', summary: '靈感三:[來自網路] 使用 n8n 這類視覺化工作流工具。', tags: ['n8n', '自動化']),
      ];
      debugPrint('成功獲取 ${freshInsights.length} 則新靈感!');

      // 3. 成功後,更新 Hive 快取
      await _box.clear(); // 清空舊快取
      // 使用 putAll 批量寫入,key 為 insight.id
      await _box.putAll(Map.fromEntries(freshInsights.map((e) => MapEntry(e.id, e))));
      
      // 4. 更新 UI 狀態
      state = freshInsights;
    } catch (e) {
      // 5. 如果發生錯誤(例如沒有網路),則不做任何事
      // UI 會繼續顯示從快取中讀取的舊資料
      debugPrint('獲取新靈感失敗:$e。將繼續使用快取資料。');
    }
  }
}

final insightNotifierProvider = StateNotifierProvider<InsightNotifier, List<Insight>>((ref) {
  return InsightNotifier();
});

離線測試!

再次執行App。你會看到主頁先是快速顯示了上次的資料(我在假資料中加入了 [來自網路] 來區分),過了2秒後,UI會刷新並顯示新的資料。

接下來,打開手機的飛航模式,然後重啟App。會發現,即使沒有網路,App依然能正常顯示主頁的靈感卡片!控制台會印出錯誤日誌,但App對使用者來說是完全可用的,這就是離線快取的功用。

明日預告:FCM推播通知

一個好的 App 不僅要被動地提供服務,還應該能主動地與使用者溝通。如果每天都有新的靈感,我們該如何通知使用者「嘿,今天的靈感更新了!」呢?

明天將進入下一個全新的領域:推播通知。將整合 Firebase Cloud Messaging (FCM),讓我們的App即使在被關閉時,也能向使用者發送充滿吸引力的通知!


【哈囉你好:)感謝你的閱讀!其他我會常出沒的地方:Threads


上一篇
30 天做一個極簡App:讓資料活下來 - Hive 離線快取(上)
下一篇
30 天做一個極簡App:每日喚醒 FCM推播通知
系列文
Mobile Dev|日更靈感來源 App:Flutter × LLM × n8n,每天只推 3 則!19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言