iT邦幫忙

2025 iThome 鐵人賽

DAY 19
0
Mobile Development

我的 Flutter 進化論:30 天打造「Crew Up!」的架構之旅系列 第 19

Day 19: Firebase Analytics - 數據驅動的產品決策

  • 分享至 

  • xImage
  •  

在完成 Day 18 的錯誤追蹤系統後,我們已經能快速發現和修復問題。不過,除了知道「哪裡出錯」,我們更想了解「使用者如何使用 App」。今天我們要為 Crew Up! 建立使用者行為分析系統,就像 App 的數據儀表板,幫助我們用真實數據做出產品決策。

🎯 為什麼需要 Analytics?

開發功能時,我們常常會有很多假設:「使用者應該會這樣操作」、「這個功能很重要」。不過一旦 App 上線後,實際情況往往和想像不同。從我們的開發經驗來看,數據分析能幫助我們更了解真實的使用情況。

📊 我們想了解的問題

  • 使用者最常使用哪些功能?
  • 活動建立流程在哪一步流失率最高?
  • 搜索功能的轉換率如何?
  • 使用者平均使用時長是多少?
  • 哪些類別的活動最受歡迎?

🎯 Analytics 的價值

  • 用真實數據驗證我們的假設
  • 幫助我們優先處理高影響力的功能
  • 讓我們能衡量改善效果
  • 了解真實的使用場景
  • 發現我們可能忽略的潛在問題
  • 持續優化使用者體驗

📋 套件依賴設定

# pubspec.yaml
dependencies:
  firebase_analytics: ^12.0.2         # Firebase Analytics SDK
  firebase_core: ^4.1.0               # Firebase 核心
  flutter_riverpod: ^2.6.1            # 狀態管理
  shared_preferences: ^2.2.2          # 本地儲存(同意狀態)

💡 關於 Firebase 專案設定,請參考 Day 13 - Firebase 專案設定

💻 核心實作

1. AnalyticsService:核心服務

// lib/app/core/services/analytics_service.dart

/// Firebase Analytics 服務
class AnalyticsService {
  final FirebaseAnalytics _analytics;

  AnalyticsService({FirebaseAnalytics? analytics})
    : _analytics = analytics ?? FirebaseAnalytics.instance;

  /// 記錄自定義事件
  Future<void> logEvent({
    required String eventName,
    Map<String, Object?>? parameters,
  }) async {
    try {
      await _analytics.logEvent(
        name: eventName,
        parameters: parameters?.map((key, value) => MapEntry(key, value ?? '')),
      );
    } on Exception catch (e) {
      developer.log('Analytics error: $e', name: 'Analytics');
    }
  }

  /// 記錄頁面瀏覽
  Future<void> logScreenView({
    required String screenName,
    String? screenClass,
  }) async {
    await logEvent(
      eventName: 'screen_view',
      parameters: {
        'screen_name': screenName,
        if (screenClass != null) 'screen_class': screenClass,
      },
    );
  }

  /// 記錄活動相關事件
  Future<void> logActivityEvent({
    required String action,
    required String activityId,
    String? category,
  }) async {
    await logEvent(
      eventName: 'activity_$action',
      parameters: {
        'activity_id': activityId,
        if (category != null) 'category': category,
      },
    );
  }

  /// 記錄搜索事件
  Future<void> logSearch({
    required String searchTerm,
    String? category,
  }) async {
    await logEvent(
      eventName: 'search',
      parameters: {
        'search_term': searchTerm,
        if (category != null) 'category': category,
      },
    );
  }

  /// 設置用戶屬性
  Future<void> setUserProperty({
    required String name,
    required String value,
  }) async {
    try {
      await _analytics.setUserProperty(name: name, value: value);
    } on Exception catch (e) {
      developer.log('Analytics error: $e', name: 'Analytics');
    }
  }

  /// 設置用戶ID
  Future<void> setUserId(String userId) async {
    try {
      await _analytics.setUserId(id: userId);
    } on Exception catch (e) {
      developer.log('Analytics error: $e', name: 'Analytics');
    }
  }

  /// 記錄登入
  Future<void> logLogin({String? method}) async {
    await _analytics.logLogin(loginMethod: method);
  }

  /// 記錄註冊
  Future<void> logSignUp({String? method}) async {
    await _analytics.logSignUp(signUpMethod: method ?? 'unknown');
  }

  /// 啟用/停用分析收集
  Future<void> setAnalyticsCollectionEnabled(bool enabled) async {
    try {
      await _analytics.setAnalyticsCollectionEnabled(enabled);
    } on Exception catch (e) {
      developer.log('Analytics error: $e', name: 'Analytics');
    }
  }
}

@riverpod
AnalyticsService analyticsService(Ref ref) => AnalyticsService();

2. Domain 層:業務邏輯

我們使用 Clean Architecture 的 Domain 層來管理 Analytics 同意的業務邏輯:

核心概念:

  • AnalyticsConsentRepository:定義同意管理的抽象介面
  • ManageAnalyticsConsentUseCase:封裝業務規則和邏輯
  • AnalyticsConsent:代表同意狀態的實體

主要功能:

  • 同意狀態的獲取和設定
  • GDPR 合規的同意過期檢查(每年重新確認)
  • 數據清除和隱私保護

3. Data 層:實作

AnalyticsConsentManager 實作 Domain 層的介面,負責實際的數據儲存和管理:

核心功能:

  • 使用 SharedPreferences 儲存同意狀態和時間戳
  • 自動檢查同意是否過期(GDPR 要求每年重新確認)
  • AnalyticsService 整合,控制數據收集的啟用/停用
  • 提供 Riverpod Providers 供 UI 層使用

關鍵實作細節:

  • 同意過期檢查:超過 365 天自動標記為需要重新確認
  • 隱私保護:拒絕同意時自動清除所有 Analytics 數據
  • 狀態恢復:App 重啟時自動恢復之前的同意狀態

4. Presentation 層:UI 和狀態管理

AnalyticsConsentDialogProvider:管理同意對話框的顯示狀態,避免重複顯示。

核心邏輯:

  • 檢查使用者是否已同意,未同意時才顯示對話框
  • 防止重複顯示,確保良好的使用者體驗
  • 整合錯誤處理,確保 App 穩定性

同意對話框

AnalyticsConsentDialog:向使用者清楚說明數據收集的目的和範圍,提供明確的同意選項。

設計要點:

  • 清楚說明收集和不收集的數據類型
  • 提供「拒絕」和「我同意」兩個明確選項
  • 說明使用者可以隨時在設定中更改選擇

AnalyticsSettingsTile:在設定頁面提供開關,讓使用者可以隨時更改同意狀態。

5. App 整合

AnalyticsConsentInitializer:在 App 啟動時自動處理 Analytics 同意流程。

關鍵功能:

  • 恢復之前儲存的同意狀態
  • 在適當時機顯示同意對話框
  • 確保 Navigator 可用後才顯示對話框
  • 避免重複檢查,提升效能

App 整合方式

main.dart 中,我們確保 Firebase 初始化時預設停用 Analytics 收集,等待使用者同意:

// lib/main.dart

// (imports omitted)

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Initialize Firebase (idempotent)
  await ensureFirebaseInitialized();
  
  // GDPR Compliance: app_open event will be logged after user consent
  // is obtained in AnalyticsConsentInitializer
  developer.log(
    '📊 Analytics: Waiting for user consent before logging events',
    name: 'Analytics',
  );
  
  runApp(UncontrolledProviderScope(container: container, child: const App()));
}

app.dart 中,我們用 AnalyticsConsentInitializer 包裹整個 App:

// lib/app.dart

// (imports omitted)

class App extends ConsumerWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) =>
      AnalyticsConsentInitializer(
        child: MaterialApp.router(
          // App 配置...
        ),
      );
}

📊 最佳實務與規範

事件命名規範

基於 Firebase 的限制和我們專案中的實務經驗:

✅ 我們推薦的作法

// 使用小寫字母和底線
'activity_created'
'join_activity_completed'

// 使用預定義常量
{
  'activity_id': 'activity_123',
  'category': 'sports',
}

❌ 我們建議避免的作法

// 包含空格和大寫
'Activity Created'
'Join Activity'

// 使用保留前綴
'firebase_custom_event'
'google_my_event'

Firebase Analytics 限制

在我們實作過程中需要注意的限制:

  • 事件名稱:最多 40 個字元
  • 參數名稱:最多 40 個字元
  • 參數數量:每個事件最多 25 個參數
  • 參數值:最多 100 個字元
  • 使用者屬性:最多 25 個自訂屬性
  • 使用者屬性名稱:最多 24 個字元
  • 使用者屬性值:最多 36 個字元

隱私保護原則

✅ 我們推薦的作法

// 不記錄個人身分資訊
await analytics.logEvent(
  eventName: 'user_signed_up',
  parameters: {
    'method': 'email',
    'timestamp': DateTime.now().toIso8601String(),
  },
);

❌ 我們建議避免的作法

// 不要記錄 PII
await analytics.logEvent(
  eventName: 'user_signed_up',
  parameters: {
    'email': 'user@example.com',  // ❌ 不要記錄
    'phone': '+886912345678',     // ❌ 不要記錄
    'real_name': '王小明',         // ❌ 不要記錄
  },
);

🧪 測試與驗證

在 Firebase Console 驗證

在 Firebase Console 驗證

  1. 開啟 Firebase Console
  2. 進入 Analytics
  3. 選擇你的裝置
  4. 即時查看事件流

使用 DebugView 即時驗證

Firebase Analytics 提供 DebugView 功能,可以即時查看事件:

啟用 DebugView 的步驟:

  1. 在開發環境中啟用 Analytics Debug 模式
  2. 使用 Firebase Console 的 DebugView 查看即時事件
  3. 驗證事件參數和用戶屬性是否正確傳送

DebugView 的使用技巧:

  • 可以即時看到事件觸發情況
  • 檢查事件參數是否符合預期
  • 驗證用戶屬性設定是否正確

常見問題排查

事件沒有顯示在 Console

  • 檢查 Firebase 設定是否正確
  • 確認已啟用 DebugView(開發模式)
  • 等待最多 24 小時(正式環境非即時)
  • 檢查事件名稱是否符合規範

使用者屬性沒有更新

  • 確認已設定使用者 ID(setUserId
  • 檢查屬性名稱和值是否符合長度限制
  • 等待數據同步(可能需要數小時)

🎯 總結

今天我們為 Crew Up! 建立了完整的 Analytics 系統:

✅ 已完成的功能

  • 完整的 Analytics 服務封裝,支援多種事件類型和用戶屬性
  • GDPR 合規的同意管理系統,確保用戶隱私保護
  • 多層次的架構設計(Domain/Data/Presentation),遵循 Clean Architecture
  • 豐富的事件追蹤和用戶屬性設定,支援業務指標分析
  • 完善的錯誤處理和日誌記錄,確保系統穩定性
  • 預設拒絕數據收集,等待用戶明確同意後才啟用

🎯 技術亮點

  • 使用 Riverpod 進行狀態管理,提供響應式的 UI 更新
  • 實作同意過期檢查機制(365天),符合 GDPR 要求
  • 提供完整的漏斗分析功能,支援用戶旅程追蹤
  • 整合性能監控和錯誤追蹤,形成完整的數據收集體系

關鍵學習

從實際開發中,我們學到:

  1. GDPR 合規 - 預設拒絕數據收集,明確取得使用者同意,提供隨時撤銷機制
  2. 隱私優先 - 預設拒絕,明確取得同意
  3. 錯誤處理 - Analytics 失敗不應影響 App 正常運作
  4. 數據驅動決策 - 用真實數據而非假設來優化產品

下一步

明天(Day 20),我們將探討 Firebase Performance Monitoring,學習如何追蹤 App 的效能指標,發現並優化效能瓶頸,讓 Crew Up! 在各種裝置上都能流暢運行。

期待與您在 Day 20 相見!


📋 相關資源

📝 專案資訊

  • 專案名稱: Crew Up!
  • 開發日誌: Day 19 - Firebase Analytics:數據驅動的產品決策
  • 文章日期: 2025-10-03
  • 技術棧: Flutter 3.8+, Firebase Analytics 12.0.2, Riverpod 2.6.1

上一篇
Day 18: Firebase Crashlytics - 錯誤追蹤與分析系統
下一篇
Day 20 - Firebase Performance Monitoring:數據驅動的效能優化
系列文
我的 Flutter 進化論:30 天打造「Crew Up!」的架構之旅22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言