Flutter SDK - 卸载跟踪

文档

卸载跟踪

通过将推送通知服务与 Singular SDK 集成,跟踪应用程序的卸载情况,以衡量用户留存率并优化重新参与活动。

重要:谷歌于 2018 年 4 月废弃了 GCM API。使用 Firebase Cloud Messaging (FCM) 实现所有 Android 卸载跟踪。


安卓卸载跟踪

前提条件

在您的 Flutter 应用程序中实施卸载跟踪之前,请按照 "设置 Android 卸载跟踪 "指南在 Singular 平台中配置您的应用程序。


系统要求

卸载跟踪需要 Firebase Cloud Messaging 和特定设备配置。

FCM 要求(源):

  • 安卓版本:设备必须运行 Android 4.1 (API 16) 或更高版本
  • Google Play 服务:设备必须安装 Google Play Store 应用程序
  • 模拟器支持:支持带有 Google API 的 Android 4.1 或更高版本模拟器
  • 分发:应用程序可在 Google Play Store 之外分发,同时仍支持卸载跟踪

注意:不支持的 Android 版本或没有 Google Play 服务的设备上的用户将不会被跟踪卸载。


实施步骤

步骤 1:安装 Firebase 软件包

pubspec.yaml 文件中添加 Firebase 依赖项,以获得核心功能和消息支持。

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 控制台项目中注册 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 卸载跟踪基于苹果推送通知服务(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() 在 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) {
      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 令牌已格式化为十六进制字符串,这是 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 平台:确保您的应用程序拥有必要的推送通知权限,并在 Apple Developer 账户中正确配置 APN
  • 安卓:验证 FCM 是否已在 Firebase 控制台中设置,以及google-services.json 文件是否已包含在项目中,网址是android/app/

验证和故障排除

验证实施

在部署到生产环境之前,请确认卸载跟踪工作正常。

  1. 检查日志:验证令牌注册是否以正确格式出现在控制台日志中
  2. 测试令牌生成:确保在授予权限后首次启动应用程序时生成令牌
  3. 监控仪表板:在 24-48 小时后检查 Singular 仪表板上的卸载跟踪数据
  4. 测试令牌刷新:清除应用程序数据并验证重新启动应用程序时令牌更新是否正确

常见问题

  • 令牌未生成:验证 Firebase 依赖项是否已正确安装,Firebase 是否已在 Flutter 项目中配置。添加依赖项后运行flutter pub get
  • 权限被拒绝:检查用户是否已授予通知权限。对于 Android 13+,需要明确的权限请求。对于 iOS,用户必须授权通知
  • 令牌未更新:确保您已订阅两个平台的onTokenRefresh 流。应在应用程序初始化时设置监听器
  • 数据缺失:确认设备符合平台要求(Android 4.1+ 支持 Google Play 服务,iOS 支持 APN)。没有这些服务的设备无法跟踪
  • 配置错误:确认已在应用程序的 Singular 平台设置中启用卸载跟踪。请按照 "先决条件 "中链接的特定平台设置指南进行操作
  • Firebase 设置:对于 Android,确保google-services.json 位于android/app/ 中。对于 iOS,确保GoogleService-Info.plist 已添加到 Xcode 项目 Runner 文件夹中。
  • SDK 初始化:在调用registerDeviceTokenForUninstall() 之前,确认 Singular SDK 已初始化。令牌注册应在 SDK 启动后进行
  • 平台检测:dart:io 使用Platform.isAndroidPlatform.isIOS ,以确保特定平台的代码在正确的平台上运行。

其他资源:有关详细的故障排除,请参阅《Android 卸载跟踪设置指南》、《iOS 卸载跟踪设置指南》Flutter Firebase 设置文档


最佳实践

令牌管理

  • 尽早注册:在应用程序生命周期中尽早申请权限和注册令牌,最好在首次启动应用程序时就注册。
  • 错误处理:围绕令牌检索和注册实施稳健的错误处理,以便从容应对故障
  • 令牌刷新:始终实施令牌刷新监听器,以便在令牌发生变化时保持 Singular 更新
  • 用户体验:在上下文中请求通知权限,解释应用程序需要这些权限的原因,以提高权限授予率

测试策略

  • 开发测试:在安卓和 iOS 实体设备上进行测试,因为模拟器对推送通知的支持可能有限
  • 权限流程:测试用户拒绝权限的场景,并验证应用程序是否能从容应对
  • 令牌持久性:验证令牌在应用程序重启时是否会持续存在,并在刷新时正确更新
  • 日志记录:在开发过程中启用详细日志记录,以跟踪令牌生成和注册情况