Flutter SDK - アンインストールトラッキング

ドキュメント

アンインストール追跡

プッシュ通知サービスをSingular SDKと統合することで、アプリのアンインストールを追跡してユーザーのリテンションを測定し、リエンゲージメントキャンペーンを最適化します。

重要:Google は 2018 年 4 月に GCM API を廃止しました。Android のアンインストールトラッキングの実装にはすべて Firebase Cloud Messaging (FCM) を使用してください。


Android アンインストールトラッキング

前提条件

Flutterアプリにアンインストールトラッキングを実装する前に、Setting Up Android Uninstall Trackingのガイドに従ってSingularプラットフォームでアプリを設定してください。


システム要件

アンインストールトラッキングには、Firebase Cloud Messaging と特定のデバイス設定が必要です。

FCMの要件(ソース):

  • Android バージョン:Android 4.1 (API 16) 以降を実行しているデバイスが必要です。
  • Google Play サービス:Google Playストアアプリがインストールされている必要があります。
  • エミュレータのサポート:Google APIを搭載したAndroid 4.1以上のエミュレータに対応しています。
  • 配布:アンインストール追跡をサポートしながら、Google Play ストア以外でもアプリを配布できます。

注意:サポートされていない Android バージョンまたは Google Play サービスを利用していないデバイスのユーザーは、アンインストールを追跡されません。


実装ステップ

ステップ 1: Firebase パッケージのインストール

コア機能とメッセージングサポートのために、Firebase の依存関係をpubspec.yaml ファイルに追加します。

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^2.24.2
  firebase_messaging: ^14.7.10
  singular_flutter_sdk: ^1.8.0

依存関係を追加したら、flutter pub get を実行してパッケージをインストールします。


ステップ 2: Firebase の設定

Android用FlutterプロジェクトにFirebase設定ファイルを追加する。

  1. Firebase ConsoleプロジェクトにAndroidアプリを登録する。
  2. google-services.json をダウンロードし、android/app/に配置する。
  3. Firebase メッセージングの依存関係がプロジェクトに追加されていることを確認する。

詳細なセットアップ手順については、FirebaseをFlutterアプリに追加するを参照してください。


ステップ3: Firebaseを初期化する

Flutterアプリを実行する前にFirebaseを初期化し、メッセージングサービスを有効にします。

Dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize Firebase
  await Firebase.initializeApp();

  runApp(MyApp());
}

ステップ 4: 通知許可をリクエストする

FCMトークンを取得する前に、ユーザーに通知許可をリクエストします(Android 13以上で必要)。

Dart
import 'package:firebase_messaging/firebase_messaging.dart';
import 'dart:io';

Future<bool> requestNotificationPermission() async {
  if (Platform.isAndroid) {
    // Android 13+ requires explicit permission request
    // Note: firebase_messaging handles this automatically via requestPermission
    final FirebaseMessaging messaging = FirebaseMessaging.instance;
    final NotificationSettings settings = await messaging.requestPermission(
      alert: true,
      badge: true,
      sound: true,
    );

    return settings.authorizationStatus == AuthorizationStatus.authorized ||
           settings.authorizationStatus == AuthorizationStatus.provisional;
  }

  // iOS permission handled separately
  return true;
}

ステップ5:FCMトークンの取得と登録

FCMデバイストークンを取得し、パーミッションを要求した後、registerDeviceTokenForUninstall() を使用してSingularに登録します。

Dart
import 'package:flutter/material.dart';
import 'package:singular_flutter_sdk/singular.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'dart:io';

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();

    if (Platform.isAndroid) {
      initializeAndroidUninstallTracking();
    }
  }

  Future<void> initializeAndroidUninstallTracking() async {
    try {
      // Request notification permission
      final hasPermission = await requestNotificationPermission();

      if (!hasPermission) {
        print('Notification permission denied - uninstall tracking unavailable');
        return;
      }

      // Get FCM token
      final token = await FirebaseMessaging.instance.getToken();

      if (token != null) {
        // Register token with Singular for uninstall tracking
        Singular.registerDeviceTokenForUninstall(token);
        print('FCM token registered with Singular: $token');
      } else {
        print('No FCM token available');
      }
    } catch (error) {
      print('Error setting up uninstall tracking: $error');
    }
  }

  Future<bool> requestNotificationPermission() async {
    final messaging = FirebaseMessaging.instance;
    final settings = await messaging.requestPermission(
      alert: true,
      badge: true,
      sound: true,
    );

    return settings.authorizationStatus == AuthorizationStatus.authorized ||
           settings.authorizationStatus == AuthorizationStatus.provisional;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

メソッドの署名

static void registerDeviceTokenForUninstall(String token)

メソッドの完全なドキュメントは、registerDeviceTokenForUninstallリファレンスを参照してください。


ステップ 6: トークンの更新処理

FCMトークンが更新されるたびにSingularで更新し、正確なアンインストールトラッキングを維持します。

Dart
import 'package:flutter/material.dart';
import 'package:singular_flutter_sdk/singular.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'dart:io';

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();

    if (Platform.isAndroid) {
      setupTokenRefreshListener();
    }
  }

  void setupTokenRefreshListener() {
    // Listen for token refresh events
    FirebaseMessaging.instance.onTokenRefresh.listen((String token) {
      print('FCM token refreshed: $token');

      // Update Singular with new token
      Singular.registerDeviceTokenForUninstall(token);
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

ベストプラクティスです:FCM トークンはいつでも更新できます(アプリの更新、デバイスの復元など)。常にonTokenRefresh ストリームを購読し、Singular を最新のトークンで更新してください。


iOS のアンインストールトラッキング

前提条件

iOSアンインストールトラッキングの設定」のガイドに従って、SingularプラットフォームでiOSアプリを設定します。

iOS のアンインストールトラッキングは、Apple Push Notification サービス(APNs)技術に基づいています。アプリがプッシュ通知に対応していない場合は、Apple の「APNs にアプリを登録する」ガイドを参照してください。


実装ステップ

ステップ1: iOSプロジェクトの設定

iOSプロジェクトにFirebaseの設定を追加し、プッシュ通知機能を有効にします。

  1. Firebase Console プロジェクトに iOS アプリを登録します。
  2. GoogleService-Info.plist をダウンロードし、Xcode Runner フォルダに追加します。
  3. Xcode プロジェクトの設定で、プッシュ通知機能を有効にします。
  4. バックグラウンドモードを有効にし、リモート通知をチェックします。

ステップ 2: iOS 通知許可をリクエストする

ユーザーに通知許可をリクエストし、APNSデバイストークンを取得する。

Dart
import 'package:firebase_messaging/firebase_messaging.dart';
import 'dart:io';

Future<bool> requestIOSNotificationPermission() async {
  if (!Platform.isIOS) {
    return false;
  }

  try {
    final FirebaseMessaging messaging = FirebaseMessaging.instance;
    final NotificationSettings settings = await messaging.requestPermission(
      alert: true,
      badge: true,
      sound: true,
      provisional: false,
    );

    final authorized =
        settings.authorizationStatus == AuthorizationStatus.authorized ||
        settings.authorizationStatus == AuthorizationStatus.provisional;

    if (authorized) {
      print('iOS notification authorization status: ${settings.authorizationStatus}');
      return true;
    }

    return false;
  } catch (error) {
    print('Error requesting iOS notification permission: $error');
    return false;
  }
}

ステップ3:APNSトークンの取得と登録

APNSデバイストークンを取得し、認証後にregisterDeviceTokenForUninstall()

Dart
import 'package:flutter/material.dart';
import 'package:singular_flutter_sdk/singular.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'dart:io';

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();

    if (Platform.isIOS) {
      initializeIOSUninstallTracking();
    }
  }

  Future<void> initializeIOSUninstallTracking() async {
    try {
      // Request notification authorization
      final hasPermission = await requestIOSNotificationPermission();

      if (!hasPermission) {
        print('Notification permission denied - uninstall tracking unavailable');
        return;
      }

      // Get APNS token
      final apnsToken = await FirebaseMessaging.instance.getAPNSToken();

      if (apnsToken != null) {
        // Register token with Singular for uninstall tracking
        Singular.registerDeviceTokenForUninstall(apnsToken);
        print('APNS token registered with Singular: $apnsToken');
      } else {
        print('No APNS token available');
      }
    } catch (error) {
      print('Error setting up iOS uninstall tracking: $error');
    }
  }

  Future<bool> requestIOSNotificationPermission() async {
    final messaging = FirebaseMessaging.instance;
    final settings = await messaging.requestPermission(
      alert: true,
      badge: true,
      sound: true,
    );

    return settings.authorizationStatus == AuthorizationStatus.authorized ||
           settings.authorizationStatus == AuthorizationStatus.provisional;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

トークンのフォーマット getAPNSToken() から取得したAPNSトークンは、すでに16進文字列としてフォーマットされており、これはSingularの正しいフォーマットです。


ステップ4:トークン更新の処理(iOS)

アプリのライフサイクル中にAPNSトークンが変更された場合、Singularで更新します。

Dart
import 'package:flutter/material.dart';
import 'package:singular_flutter_sdk/singular.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'dart:io';

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();

    if (Platform.isIOS) {
      setupIOSTokenRefreshListener();
    }
  }

  void setupIOSTokenRefreshListener() {
    // Listen for token refresh events
    FirebaseMessaging.instance.onTokenRefresh.listen((String token) {
      print('APNS token refreshed: $token');

      // Update Singular with new token
      Singular.registerDeviceTokenForUninstall(token);
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

完全なクロスプラットフォーム実装

統一されたアンインストールトラッキングセットアップ

適切なエラー処理とトークン更新ロジックを使用して、Android と iOS の両方のプラットフォームでアンインストールトラッキングを実装します。

Dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:singular_flutter_sdk/singular.dart';
import 'package:singular_flutter_sdk/singular_config.dart';
import 'dart:io';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize Firebase
  await Firebase.initializeApp();

  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();

    // Initialize Singular SDK
    initializeSingularSDK();

    // Setup uninstall tracking
    initializeUninstallTracking();

    // Setup token refresh listener
    setupTokenRefreshListener();
  }

  void initializeSingularSDK() {
    final config = SingularConfig(
      'YOUR_SDK_KEY',
      'YOUR_SDK_SECRET'
    );

    Singular.start(config);
  }

  Future<void> initializeUninstallTracking() async {
    try {
      if (Platform.isAndroid) {
        await setupAndroidUninstallTracking();
      } else if (Platform.isIOS) {
        await setupIOSUninstallTracking();
      }
    } catch (error) {
      print('Error initializing uninstall tracking: $error');
    }
  }

  Future<void> setupAndroidUninstallTracking() async {
    print('Setting up Android uninstall tracking');

    // Request notification permission
    final messaging = FirebaseMessaging.instance;
    final settings = await messaging.requestPermission(
      alert: true,
      badge: true,
      sound: true,
    );

    if (settings.authorizationStatus != AuthorizationStatus.authorized &&
        settings.authorizationStatus != AuthorizationStatus.provisional) {
      print('Android notification permission denied');
      return;
    }

    // Get and register FCM token
    final token = await messaging.getToken();

    if (token != null) {
      Singular.registerDeviceTokenForUninstall(token);
      print('Android FCM token registered: $token');
    } else {
      print('Failed to retrieve Android FCM token');
    }
  }

  Future<void> setupIOSUninstallTracking() async {
    print('Setting up iOS uninstall tracking');

    // Request iOS notification authorization
    final messaging = FirebaseMessaging.instance;
    final settings = await messaging.requestPermission(
      alert: true,
      badge: true,
      sound: true,
    );

    final authorized =
        settings.authorizationStatus == AuthorizationStatus.authorized ||
        settings.authorizationStatus == AuthorizationStatus.provisional;

    if (!authorized) {
      print('iOS notification permission denied');
      return;
    }

    // Get and register APNS token
    final apnsToken = await messaging.getAPNSToken();

    if (apnsToken != null) {
      Singular.registerDeviceTokenForUninstall(apnsToken);
      print('iOS APNS token registered: $apnsToken');
    } else {
      print('Failed to retrieve iOS APNS token');
    }
  }

  void setupTokenRefreshListener() {
    // Listen for token refresh events (works for both platforms)
    FirebaseMessaging.instance.onTokenRefresh.listen((String token) {
      print('${Platform.operatingSystem.toUpperCase()} token refreshed: $token');
      Singular.registerDeviceTokenForUninstall(token);
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Uninstall Tracking Demo'),
        ),
        body: Center(
          child: Text('Uninstall tracking initialized'),
        ),
      ),
    );
  }
}

プラットフォーム固有の注意事項

  • iOSアプリに必要なプッシュ通知権限があり、APN が Apple Developer アカウントで適切に設定されていることを確認します。
  • AndroidFirebaseコンソールでFCMが設定され、android/app/google-services.json ファイルがプロジェクトに含まれていることを確認してください。

検証とトラブルシューティング

実装の確認

本番環境にデプロイする前に、アンインストールトラッキングが正しく動作していることを確認します。

  1. ログを確認する:トークン登録が正しい形式でコンソールログに表示されることを確認します。
  2. トークン生成のテスト:権限を付与した後、最初のアプリ起動時にトークンが生成されることを確認します。
  3. ダッシュボードを監視する:24~48時間後のアンインストール追跡データをSingularダッシュボードで確認する
  4. トークン更新のテスト:アプリのデータを消去し、アプリの再起動時にトークンが正しく更新されることを確認する

よくある問題

  • トークンが生成されない:Firebaseの依存関係が正しくインストールされ、FlutterプロジェクトにFirebaseが設定されていることを確認する。依存関係を追加した後にflutter pub get を実行してください。
  • パーミッションが拒否されている:ユーザーが通知許可を与えているか確認する。Android 13以上の場合、明示的な許可要求が必要です。iOSの場合、ユーザーは通知を許可する必要があります。
  • トークンが更新されない:両プラットフォームのonTokenRefresh ストリームを購読していることを確認してください。リスナーはアプリの初期化で設定する必要があります。
  • データがない:デバイスがプラットフォーム要件(Android 4.1+ with Google Play Services、iOS with APNs support)を満たしていることを確認してください。これらのサービスがないデバイスは追跡できません。
  • 設定エラー:アプリのSingularプラットフォーム設定でアンインストールトラッキングが有効になっていることを確認してください。前提条件にリンクされているプラットフォーム固有のセットアップガイドに従ってください。
  • Firebaseのセットアップ:Android の場合、google-services.jsonandroid/app/ にあることを確認してください。iOS の場合、GoogleService-Info.plist が Xcode プロジェクトの Runner フォルダに追加されていることを確認してください。
  • SDKの初期化: registerDeviceTokenForUninstall() を呼び出す前に、Singular SDK が初期化されていることを確認してください。トークン登録はSDK起動後に行う。
  • プラットフォームの検出: dart:io からPlatform.isAndroidPlatform.isIOS を使用して、プラットフォーム固有のコードが正しいプラットフォームで実行されるようにする。

その他のリソース:詳細なトラブルシューティングについては、Android Uninstall Tracking Setup GuideiOS Uninstall Tracking Setup GuideFlutter Firebase Setup Documentationを参照してください。


ベストプラクティス

トークン管理

  • 早期登録:アプリライフサイクルのできるだけ早い段階、理想的には最初のアプリ起動時にパーミッションのリクエストとトークンの登録を行う。
  • エラー処理:トークンの取得と登録に関する堅牢なエラー処理を実装し、失敗を優雅に処理する。
  • トークンのリフレッシュ:トークンが変更されたときにSingularをアップデートし続けるために、常にトークンリフレッシュリスナーを実装する。
  • ユーザーエクスペリエンス:許可率を向上させるために、アプリがなぜ通知許可を必要とするのかを説明しながら、コンテキストで通知許可を要求する。

テスト戦略

  • 開発テスト:エミュレータではプッシュ通知のサポートが制限される可能性があるため、物理的なAndroidとiOSの両方のデバイスでテストする。
  • パーミッションフロー:ユーザーがパーミッションを拒否するシナリオをテストし、アプリがこれを優雅に処理することを検証する。
  • トークンの永続性:トークンがアプリの再起動後も持続し、更新時に正しく更新されることを確認する。
  • ロギング:トークンの生成と登録を追跡するために、開発中に冗長ロギングを有効にする。