Stories

Detail Return Return

Flutter應用架構設計:基於Riverpod的狀態管理最佳實踐 - Stories Detail

Flutter應用架構設計:基於Riverpod的狀態管理最佳實踐

本文基於BeeCount(蜜蜂記賬)項目的實際開發經驗,深入探討如何使用Riverpod構建可維護、可擴展的Flutter應用架構。

項目背景

BeeCount(蜜蜂記賬)是一款開源、簡潔、無廣告的個人記賬應用。所有財務數據完全由用户掌控,支持本地存儲和可選的雲端同步,確保數據絕對安全。

引言

在現代Flutter應用開發中,狀態管理是決定項目成敗的關鍵因素之一。傳統的setState已無法滿足複雜應用的需求,而各種狀態管理解決方案(Provider、Bloc、GetX、Riverpod等)都有各自的優缺點。

BeeCount作為一個功能完整的財務管理應用,涉及數據庫操作、雲同步、主題切換、國際化等多個複雜場景。經過實際開發驗證,Riverpod在提供強類型安全、編譯時錯誤檢查、依賴注入等特性的同時,還保持了出色的性能和開發體驗。

Riverpod核心概念

Provider類型選擇

在BeeCount中,我們根據不同的使用場景選擇合適的Provider類型:

1. StateProvider - 簡單狀態管理
// 主題模式Provider - 用於簡單的狀態值
final themeModeProvider = StateProvider<ThemeMode>((ref) => ThemeMode.system);

// 主色Provider - 支持個性化換裝
final primaryColorProvider = StateProvider<Color>((ref) => BeeTheme.honeyGold);

// 是否隱藏金額顯示
final hideAmountsProvider = StateProvider<bool>((ref) => false);

適用場景

  • 簡單的狀態值(bool、int、enum等)
  • 不需要複雜邏輯的狀態
  • UI開關、配置選項等
2. Provider - 依賴注入
// 數據庫Provider - 單例模式
final databaseProvider = Provider<BeeDatabase>((ref) {
  final db = BeeDatabase();
  db.ensureSeed(); // 初始化種子數據
  ref.onDispose(() => db.close()); // 自動清理資源
  return db;
});

// 倉儲Provider - 依賴數據庫
final repositoryProvider = Provider<BeeRepository>((ref) {
  final db = ref.watch(databaseProvider);
  return BeeRepository(db);
});

適用場景

  • 依賴注入
  • 單例服務
  • 不會變化的配置對象
3. FutureProvider - 異步初始化
// 主題色持久化初始化
final primaryColorInitProvider = FutureProvider<void>((ref) async {
  final prefs = await SharedPreferences.getInstance();
  final saved = prefs.getInt('primaryColor');
  if (saved != null) {
    ref.read(primaryColorProvider.notifier).state = Color(saved);
  }
  
  // 監聽變化並持久化
  ref.listen<Color>(primaryColorProvider, (prev, next) async {
    final colorValue = (next.a * 255).toInt() << 24 | 
                      (next.r * 255).toInt() << 16 | 
                      (next.g * 255).toInt() << 8 | 
                      (next.b * 255).toInt();
    await prefs.setInt('primaryColor', colorValue);
  });
});

適用場景

  • 應用初始化
  • 異步資源加載
  • 一次性的異步操作
4. StreamProvider - 實時數據
// 交易記錄流Provider
final transactionsStreamProvider = StreamProvider.family<List<Transaction>, TransactionQuery>((ref, query) {
  final repo = ref.watch(repositoryProvider);
  return repo.watchTransactions(query);
});

適用場景

  • 數據庫查詢結果
  • 實時數據更新
  • WebSocket連接等

模塊化Provider組織

BeeCount採用了模塊化的Provider組織方式,將相關的Provider按功能分組:

目錄結構

lib/providers/
├── all_providers.dart          # 統一導出
├── theme_providers.dart        # 主題相關
├── database_providers.dart     # 數據庫相關
├── statistics_providers.dart   # 統計相關
├── sync_providers.dart         # 同步相關
├── ui_state_providers.dart     # UI狀態相關
└── import_export_providers.dart # 導入導出相關

統一導出策略

// all_providers.dart
export 'theme_providers.dart';
export 'database_providers.dart';
export 'statistics_providers.dart';
export 'sync_providers.dart';
export 'ui_state_providers.dart';
export 'import_export_providers.dart';

// providers.dart - 主導出文件
export 'providers/all_providers.dart';

優勢

  • 模塊化管理,職責清晰
  • 便於維護和擴展
  • 避免循環依賴
  • 支持按需導入

高級使用模式

1. Provider組合模式

// 應用初始化Provider - 組合多個初始化邏輯
final appInitProvider = FutureProvider<void>((ref) async {
  // 激活監聽器
  ref.read(_ledgerChangeListener);
  
  // 可以添加其他初始化邏輯
  await ref.read(primaryColorInitProvider.future);
  // await ref.read(otherInitProvider.future);
});

2. 監聽器模式

// 當賬本切換時觸發同步狀態刷新
final _ledgerChangeListener = Provider<void>((ref) {
  ref.read(_currentLedgerPersist); // 激活持久化
  
  ref.listen<int>(currentLedgerIdProvider, (prev, next) {
    ref.read(syncStatusRefreshProvider.notifier).state++;
  });
});

3. 持久化模式

final _currentLedgerPersist = Provider<void>((ref) {
  // 啓動時加載
  () async {
    try {
      final prefs = await SharedPreferences.getInstance();
      final saved = prefs.getInt('current_ledger_id');
      if (saved != null) {
        ref.read(currentLedgerIdProvider.notifier).state = saved;
      }
    } catch (_) {}
  }();
  
  // 變化時持久化
  ref.listen<int>(currentLedgerIdProvider, (prev, next) async {
    try {
      final prefs = await SharedPreferences.getInstance();
      await prefs.setInt('current_ledger_id', next);
    } catch (_) {}
  });
});

性能優化策略

1. 合理使用family

// 為不同查詢條件創建獨立的Provider實例
final transactionsProvider = StreamProvider.family<List<Transaction>, TransactionQuery>(
  (ref, query) {
    final repo = ref.watch(repositoryProvider);
    return repo.watchTransactions(query);
  },
);

2. 避免不必要的重建

// 使用select僅監聽需要的部分
Consumer(
  builder: (context, ref, child) {
    // 僅當主色發生變化時重建
    final primaryColor = ref.watch(primaryColorProvider);
    return MyWidget(color: primaryColor);
  },
)

3. 資源管理

final databaseProvider = Provider<BeeDatabase>((ref) {
  final db = BeeDatabase();
  ref.onDispose(() => db.close()); // 自動清理
  return db;
});

錯誤處理和調試

1. 異常處理

final safeDataProvider = FutureProvider<Data>((ref) async {
  try {
    return await fetchData();
  } catch (error, stackTrace) {
    // 記錄錯誤
    logger.error('Failed to fetch data', error, stackTrace);
    
    // 返回默認值或重新拋出
    throw error;
  }
});

2. 開發調試

// 在開發環境添加日誌
final debugProvider = Provider<Service>((ref) {
  final service = ServiceImpl();
  
  if (kDebugMode) {
    // 添加調試監聽器
    ref.listen<State>(someStateProvider, (prev, next) {
      debugPrint('State changed: $prev -> $next');
    });
  }
  
  return service;
});

最佳實踐總結

1. 命名規範

  • Provider命名:xxxProvider
  • 內部私有Provider:_xxxProvider
  • 初始化Provider:xxxInitProvider
  • 流式Provider:xxxStreamProvider

2. 依賴管理

  • 優先使用ref.watch進行依賴注入
  • 避免直接在Provider內部創建全局依賴
  • 使用ref.onDispose進行資源清理

3. 狀態粒度

  • 保持狀態的原子性,避免大而全的狀態對象
  • 相關狀態可以分組但保持獨立
  • 使用組合模式而非繼承

4. 異步處理

  • 合理使用FutureProvider和StreamProvider
  • 避免在Provider內部使用setState
  • 使用ref.listen進行副作用處理

實際應用效果

在BeeCount項目中,採用Riverpod架構後獲得了以下收益:

  1. 開發效率提升:強類型檢查減少了運行時錯誤
  2. 代碼可維護性:模塊化組織使代碼結構清晰
  3. 性能優化:精確的依賴追蹤減少了不必要的重建
  4. 測試友好:依賴注入使單元測試更容易編寫

結語

Riverpod作為Flutter生態中的新一代狀態管理解決方案,通過其強大的特性和良好的設計,能夠很好地滿足複雜應用的需求。但關鍵在於如何合理地組織和使用這些特性,形成一套適合團隊的架構模式。

BeeCount的實踐證明,通過模塊化組織、合理的Provider類型選擇和良好的命名規範,可以構建出既易於開發又易於維護的應用架構。希望這些經驗能夠幫助到正在使用或計劃使用Riverpod的開發者們。

關於BeeCount項目

項目特色

  • 🎯 現代架構: 基於Riverpod + Drift + Supabase的現代技術棧
  • 📱 跨平台支持: iOS、Android雙平台原生體驗
  • 🔄 雲端同步: 支持多設備數據實時同步
  • 🎨 個性化定製: Material Design 3主題系統
  • 📊 數據分析: 完整的財務數據可視化
  • 🌍 國際化: 多語言本地化支持

技術棧一覽

  • 框架: Flutter 3.6.1+ / Dart 3.6.1+
  • 狀態管理: Flutter Riverpod 2.5.1
  • 數據庫: Drift (SQLite) 2.20.2
  • 雲服務: Supabase 2.5.6
  • 圖表: FL Chart 0.68.0
  • CI/CD: GitHub Actions

開源信息

BeeCount是一個完全開源的項目,歡迎開發者參與貢獻:

  • 項目主頁: https://github.com/TNT-Likely/BeeCount
  • 開發者主頁: https://github.com/TNT-Likely
  • 發佈下載: GitHub Releases

參考資源

官方文檔

  • Riverpod官方文檔 - Riverpod完整使用指南
  • Flutter狀態管理指南 - Flutter官方狀態管理對比

學習資源

  • Riverpod實戰教程 - Andrea Bizzotto的實戰指南
  • Flutter架構模式 - 官方架構概述

本文是BeeCount技術文章系列的第1篇,後續將深入探討數據庫設計、雲同步架構等話題。如果你覺得這篇文章有幫助,歡迎關注項目並給個Star!

Add a new Comments

Some HTML is okay.