Flutter SDK - 추적 제거

문서

앱 삭제 추적

앱 제거를 추적하여 사용자 리텐션을 측정하고 푸시 알림 서비스를 Singular SDK와 연동하여 리인게이지먼트 캠페인을 최적화하세요.

중요: 구글은 2018년 4월에 GCM API를 더 이상 사용하지 않습니다. 모든 안드로이드 앱 제거 추적 구현에는 Firebase 클라우드 메시징(FCM)을 사용하세요.


Android 제거 추적

전제 조건

Flutter 앱에서 앱 제거 추적을 구현하기 전에 안드로이드 앱 제거 추적 설정 가이드에 따라 Singular 플랫폼에서 앱을 구성하세요.


시스템 요구 사항

앱 제거 추적을 사용하려면 Firebase 클라우드 메시징 및 특정 기기 구성이 필요합니다.

FCM 요구 사항(소스):

  • Android 버전: 기기는 Android 4.1(API 16) 이상을 실행해야 합니다.
  • Google Play 서비스: 기기에 Google Play 스토어 앱이 설치되어 있어야 합니다.
  • 에뮬레이터 지원: Google API가 포함된 Android 4.1 이상 에뮬레이터가 지원됩니다.
  • 배포: 앱 제거 추적을 지원하면서 Google Play 스토어 외부에 앱을 배포할 수 있습니다.

참고: 지원되지 않는 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 메시징 종속성이 추가되었는지 확인합니다.

자세한 설정 지침은 Flutter 앱에 Firebase 추가를 참조하십시오.


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 토큰 검색 및 등록하기

권한을 요청한 후 registerDeviceTokenForUninstall() 을 사용하여 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) {
      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단계: 토큰 새로 고침 처리

정확한 제거 추적을 유지하기 위해 새로 고침할 때마다 Singular로 FCM 토큰을 업데이트하세요.

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 푸시 알림 서비스(APN) 기술을 기반으로 합니다. 앱이 푸시 알림을 지원하지 않는 경우 Apple의 APN에 앱 등록하기 가이드를 참조하세요.


구현 단계

1단계: iOS 프로젝트 구성

iOS 프로젝트에서 Firebase 구성을 추가하고 푸시 알림 기능을 사용 설정합니다.

  1. Firebase 콘솔 프로젝트에 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 토큰은 이미 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: 앱에 필요한 푸시 알림 권한이 있는지 확인하고 Apple 개발자 계정에서 APN이 올바르게 구성되었는지 확인합니다.
  • Android: Firebase 콘솔에서 FCM이 설정되어 있고 프로젝트에 google-services.json 파일이 android/app/에 포함되어 있는지 확인합니다.

확인 및 문제 해결

구현 확인

프로덕션에 배포하기 전에 제거 추적이 올바르게 작동하는지 확인하세요.

  1. 로그를 확인합니다: 콘솔 로그에 토큰 등록이 올바른 형식으로 표시되는지 확인합니다.
  2. 토큰 생성 테스트: 권한 부여 후 앱을 처음 실행할 때 토큰이 생성되는지 확인합니다.
  3. 대시보드 모니터링: 대시보드 모니터링: 24-48시간 후 Singular 대시보드에서 제거 추적 데이터를 확인합니다.
  4. 토큰 새로 고침 테스트: 앱 재실행 시 앱 데이터를 지우고 토큰 업데이트가 올바르게 수행되는지 확인합니다.

일반적인 문제

  • 토큰이 생성되지 않음: Firebase 종속성이 올바르게 설치되어 있고 Flutter 프로젝트에 Firebase가 구성되어 있는지 확인합니다. 종속성을 추가한 후 flutter pub get 을 실행합니다.
  • 권한이 거부되었습니다: 사용자에게 알림 권한을 부여했는지 확인합니다. Android 13 이상의 경우 명시적인 권한 요청이 필요합니다. iOS의 경우 사용자가 알림을 승인해야 합니다.
  • 토큰이 업데이트되지 않습니다: 두 플랫폼 모두 onTokenRefresh 스트림을 구독하고 있는지 확인하세요. 앱 초기화에서 수신기를 설정해야 합니다.
  • 누락된 데이터: 디바이스가 플랫폼 요구 사항을 충족하는지 확인합니다(Android 4.1+의 경우 Google Play 서비스, iOS의 경우 APNs 지원). 이러한 서비스가 없는 디바이스는 추적할 수 없습니다.
  • 구성 오류: 앱의 Singular 플랫폼 설정에서 앱 삭제 추적이 활성화되어 있는지 확인합니다. 사전 요구 사항에 링크된 플랫폼별 설정 가이드를 따르세요.
  • 파이어베이스 설정: Android의 경우 google-services.jsonandroid/app/ 에 있는지 확인합니다. iOS의 경우 GoogleService-Info.plist 이 Xcode 프로젝트 런처 폴더에 추가되었는지 확인합니다.
  • SDK 초기화: registerDeviceTokenForUninstall() 을 호출하기 전에 Singular SDK 가 초기화되었는지 확인합니다. 토큰 등록은 SDK 시작 후에 이루어져야 합니다.
  • 플랫폼 감지: dart:io 에서 Platform.isAndroidPlatform.isIOS 을 사용하여 플랫폼별 코드가 올바른 플랫폼에서 실행되는지 확인합니다.

추가 리소스: 자세한 문제 해결 방법은 Android 제거 추적 설정 가이드, iOS 제거 추적 설정 가이드Flutter 파이어베이스 설정 설명서를 참조하십시오.


모범 사례

토큰 관리

  • 조기 등록: 앱 라이프사이클 초기에 가능한 한 빨리 권한을 요청하고 토큰을 등록하세요(첫 번째 앱 실행 시).
  • 오류 처리: 토큰 검색 및 등록과 관련된 강력한 오류 처리를 구현하여 장애를 원활하게 처리합니다.
  • 토큰 새로 고침: 토큰이 변경될 때 항상 토큰 새로 고침 리스너를 구현하여 Singular를 계속 업데이트하세요.
  • 사용자 경험: 상황에 맞게 알림 권한을 요청하고 앱에 필요한 이유를 설명하여 권한 부여율을 개선합니다.

테스트 전략

  • 개발 테스트: 에뮬레이터는 푸시 알림 지원이 제한적일 수 있으므로 실제 Android 및 iOS 기기에서 테스트하세요.
  • 권한 흐름: 사용자가 권한을 거부하는 시나리오를 테스트하고 앱이 이를 정상적으로 처리하는지 확인합니다.
  • 토큰 지속성: 토큰이 앱 재시작 시에도 지속되고 새로고침 시 올바르게 업데이트되는지 확인합니다.
  • 로깅: 개발 중에 상세 로깅을 활성화하여 토큰 생성 및 등록을 추적합니다.