プッシュ通知のサポート
Firebase Cloud Messaging (FCM) とSingular SDKを統合することで、プッシュ通知によるユーザーインタラクションを追跡し、リエンゲージメントキャンペーンやコンバージョン率を正確に測定することができます。
以下の実装ガイドラインに従って、通知データが正しくSingular SDKに渡され、適切なアトリビューションが行われるようにしてください。
プッシュ通知を追跡する理由プッシュ通知はリエンゲージメントを促進しますが、トラッキングには正しい統合が必要です。Singularは、通知を受け取ったユーザーが適切にアトリビューションされるようにし、マーケティングキャンペーンとエンゲージメント戦略を最適化します。
実装ガイド
Firebaseクラウドメッセージングのセットアップ
Firebaseパッケージをインストールし、Flutterアプリケーションでプッシュ通知をサポートするためのプラットフォーム固有の設定を行います。
Firebaseパッケージのインストール
コア機能とメッセージングサポートのために、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 を実行してパッケージをインストールします。
iOSの設定
iOSアプリをFirebaseに登録し、Xcodeでプッシュ通知機能を設定します。
- iOS アプリを登録します:Firebase Console プロジェクトで iOS アプリを作成します。
-
設定ファイルの追加:
GoogleService-Info.plistをダウンロードし、Xcode Runner フォルダに追加します。 - 機能を有効にする:Xcode のプロジェクト設定で、Push Notifications 機能を有効にします。
- バックグラウンドモードを有効にする:バックグラウンドモードを有効にし、リモート通知をチェックする。
Androidの設定
AndroidアプリをFirebaseに登録し、設定ファイルをプロジェクトに追加します。
- Androidアプリを登録します:Firebase ConsoleプロジェクトにAndroidアプリを作成します。
-
設定ファイルの追加:
google-services.jsonをダウンロードし、android/app/に配置します。 - 依存関係を確認します:AndroidManifest.xmlにFirebaseメッセージングの依存関係が追加され、パーミッションが付与されていることを確認する。
FlutterでFirebaseを初期化する
Flutterアプリを実行する前にFirebaseを初期化するように設定し、アプリがフォアグラウンドでないときに受信した通知のバックグラウンドメッセージハンドラを設定する。
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
// Background message handler (must be top-level function)
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print('Background message: ${message.messageId}');
print('Data: ${message.data}');
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Firebase
await Firebase.initializeApp();
// Set background message handler
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(MyApp());
}
プッシュリンクパスの設定
プッシュ通知のペイロード構造内でSingularトラッキングリンクが配置されるJSONパスを定義します。
通知データ構造内のSingularリンクへのキーパスを指定する文字列の配列を渡すことで、プッシュリンクパスを設定します。各パスはキーの入れ子構造を表す配列です。
import 'package:singular_flutter_sdk/singular.dart';
import 'package:singular_flutter_sdk/singular_config.dart';
SingularConfig config = SingularConfig(
'YOUR_SDK_KEY',
'YOUR_SDK_SECRET'
);
// Configure paths where Singular links are located in push payload
config.pushNotificationsLinkPaths = [
['sng_link'], // Top-level key
['path', 'to', 'url'], // Nested path
['rootObj', 'nestedObj', 'singularLink'] // Deep nested path
];
Singular.start(config);
パスの構成例
-
単純なキーペイロードのトップレベルのキーには
['sng_link']を使用します。 -
ネストされたキー:ネストした JSON 構造をトラバースするには
['rootObj', 'nestedObj', 'key']を使用する。 - 複数のパス:複数のパス配列を定義して、Singularリンクのさまざまな可能性のある場所をチェックする。
設定プロパティ:
List<List<String>>? pushNotificationsLinkPaths
設定に関する完全なドキュメントは、pushNotificationsLinkPathsリファレンスを参照してください。
プラットフォーム固有の処理
Flutterでプッシュ通知を扱う
Firebaseメッセージリスナーを実装して、フォアグラウンドとバックグラウンドの通知データを取得し、アトリビューション追跡のためにSingularにデータを渡す。
import 'package:flutter/material.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:singular_flutter_sdk/singular.dart';
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
@override
void initState() {
super.initState();
setupPushNotifications();
}
void setupPushNotifications() async {
// Request permission for iOS
NotificationSettings settings = await _firebaseMessaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
print('User granted permission: ${settings.authorizationStatus}');
// Handle foreground notifications
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('Foreground message received: ${message.messageId}');
handleForegroundNotification(message);
});
// Handle notifications that opened the app from background
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
print('Notification opened app from background: ${message.messageId}');
handleBackgroundNotification(message);
});
// Check for notification that launched the app (terminated state)
RemoteMessage? initialMessage = await _firebaseMessaging.getInitialMessage();
if (initialMessage != null) {
print('App launched from notification: ${initialMessage.messageId}');
handleTerminatedNotification(initialMessage);
}
}
void handleForegroundNotification(RemoteMessage message) {
String title = message.notification?.title ?? '';
String body = message.notification?.body ?? '';
Map<String, dynamic> data = message.data;
print('Title: $title');
print('Body: $body');
print('Data: $data');
// Pass notification data to Singular
if (data.isNotEmpty) {
Singular.handlePushNotification(data);
}
// Display local notification or custom UI
displayLocalNotification(title, body, data);
}
void handleBackgroundNotification(RemoteMessage message) {
print('Processing background notification: ${message.messageId}');
// Pass notification data to Singular
if (message.data.isNotEmpty) {
Singular.handlePushNotification(message.data);
}
// Navigate to specific screen based on notification data
navigateFromNotification(message.data);
}
void handleTerminatedNotification(RemoteMessage message) {
print('Processing terminated state notification: ${message.messageId}');
// Pass notification data to Singular
if (message.data.isNotEmpty) {
Singular.handlePushNotification(message.data);
}
// Navigate to specific screen
navigateFromNotification(message.data);
}
void displayLocalNotification(
String title,
String body,
Map<String, dynamic> data
) {
// Your notification display logic
print('Displaying notification: $title - $body');
}
void navigateFromNotification(Map<String, dynamic> data) {
// Your navigation logic based on notification data
final route = data['route'];
print('Navigating to: $route');
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
メソッドのシグネチャ:
static void handlePushNotification(Map<String, dynamic> notificationData)
メソッドの完全なドキュメントについては、handlePushNotification リファレンスを参照してください。
iOSネイティブ構成
終了状態のアプリ
iOSのAppDelegateを設定して、終了状態からアプリが開いたときに、自動プッシュトラッキングのためにSingular SDKに起動オプションを渡します。
AppDelegate.m またはAppDelegate.swift で、Singular SDK に起動オプションを渡します:
Objective-C の実装
// Import at the top of the file
#import "SingularAppDelegate.h"
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Pass launch options to Singular for push tracking
[SingularAppDelegate shared].launchOptions = launchOptions;
return [super application:application
didFinishLaunchingWithOptions:launchOptions];
}
Swift の実装
import singular_flutter_sdk
override func application(_ application: UIApplication,
didFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
GeneratedPluginRegistrant.register(with: self)
// Pass launch options to Singular for push tracking
if let singularAppDelegate = SingularAppDelegate.shared() {
singularAppDelegate.launchOptions = launchOptions
}
return super.application(application,
didFinishLaunchingWithOptions: launchOptions)
}
自動処理:アプリが実行されていない状態でユーザーがプッシュ通知をタップすると、Singularは起動オプションを通してアプリ起動中に通知ペイロードを自動的にキャプチャします。
Androidネイティブの設定
アプリのバックグラウンドまたはフォアグラウンド
アプリがバックグラウンドまたはフォアグラウンド状態の時に、通知インテントをSingular SDKに渡すようにAndroid MainActivityを設定します。
MainActivityで、onNewIntent をオーバーライドして、Singularにインテントを渡します:
Javaの実装
// Add imports at the top
import android.content.Intent;
import com.singular.flutter_sdk.SingularBridge;
// Add to MainActivity class
@Override
public void onNewIntent(Intent intent) {
if(intent.getData() != null) {
setIntent(intent);
super.onNewIntent(intent);
// Pass intent to Singular for push tracking
SingularBridge.onNewIntent(intent);
}
}
Kotlinの実装
// Add imports at the top
import android.content.Intent
import com.singular.flutter_sdk.SingularBridge
// Add to MainActivity class
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.data != null) {
setIntent(intent)
// Pass intent to Singular for push tracking
SingularBridge.onNewIntent(intent)
}
}
終了状態のアプリ
終了状態のAndroidアプリには追加の設定は必要ありません。ユーザーが通知をタップすると、Flutterブリッジレイヤーがこのシナリオを自動的に処理します。
自動処理:アプリの実行中にユーザーがプッシュ通知をタップすると、Singularはネイティブブリッジの統合を通じて通知データを自動的に取得します。
検証ガイド
セッション開始時のペイロードの検証
開始セッションAPIコールを検査することで、プッシュ通知リンクがSingularに正しく渡されていることを確認します。
Singular SDKは、ユーザーが通知をタップすると、開始セッションリクエストのsingular_link パラメータの下にプッシュ通知のペイロードを含めます。
セッション開始リクエストの例:
https://sdk-api-v1.singular.net/api/v1/start?
a=<SDK-Key>
&singular_link=https://singularassist2.sng.link/C4nw9/r1m0?_dl=singular%3A%2F%2Ftest&_smtype=3
&i=net.singular.sampleapp
&s=1740905574084
&sdk=Singular/Flutter-v1.8.0
代替検証:Singular SDKコンソールを使用して、プッシュ通知のトラッキングを確認します。ディープリンクURLフィールドをチェックして、トラッキングリンクが正しくキャプチャされていることを確認します。
高度な構成
ESPドメイン設定
SingularリンクをEメールサービスプロバイダ(ESP)またはその他のサードパーティのドメインでラップする場合は、外部ドメインを設定します。
import 'package:singular_flutter_sdk/singular.dart';
import 'package:singular_flutter_sdk/singular_config.dart';
// Configure ESP domains for wrapped Singular links
SingularConfig config = SingularConfig(
'YOUR_SDK_KEY',
'YOUR_SDK_SECRET'
);
config.espDomains = ['sl.esp.link', 'custom.domain.com'];
Singular.start(config);
設定プロパティ:
List<String>? espDomains
セキュリティ 注:デフォルトでは、Singularリンク管理ページで事前に定義されたsng.linkドメインのみが許可されます。ラップリンクを使用する場合は、ESPドメインを明示的に設定します。
完全な設定ドキュメントについては、espDomainsリファレンスを参照してください。
ダイナミック・ディープ・リンク・ルーティング
動的なリダイレクトオーバーライドで1つのSingularトラッキングリンクを設定することで、1つの通知から複数のディープリンク先を実装できます。
使用例複数のアクションオプションを持つニュース速報
-
最新ニュースを読む
newsapp://article?id=12345 -
トレンドトピック
newsapp://trending -
スポーツ
newsapp://sports
複数のトラッキングリンクを作成する代わりに、1つのSingularリンクを使用し、ユーザーの選択に基づいて動的にリダイレクトを上書きします。実装の詳細については、Singular トラッキングリンクでリダイレクトを上書きするを参照してください。
完全な実装例
Firebaseのセットアップ、Singularの設定、Flutterアプリのプラットフォーム固有のハンドラを含む包括的なプッシュ通知の実装。
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';
// Background message handler (top-level function)
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print('Background message: ${message.messageId}');
// Singular handles background notifications automatically
if (message.data.isNotEmpty) {
print('Background notification data: ${message.data}');
}
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Firebase
await Firebase.initializeApp();
// Set background message handler
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
@override
void initState() {
super.initState();
initializeSingular();
setupPushNotifications();
}
void initializeSingular() {
// Configure Singular SDK
SingularConfig config = SingularConfig(
'YOUR_SDK_KEY',
'YOUR_SDK_SECRET'
);
// Configure push link paths
config.pushNotificationsLinkPaths = [
['sng_link'],
['data', 'url'],
['rootObj', 'nestedObj', 'singularLink']
];
// Configure ESP domains if needed
config.espDomains = ['sl.esp.link'];
// Enable logging for debugging
config.enableLogging = true;
// Initialize SDK
Singular.start(config);
}
void setupPushNotifications() async {
// Request permission (iOS)
NotificationSettings settings = await _firebaseMessaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
print('User granted permission');
}
// Get FCM token
String? token = await _firebaseMessaging.getToken();
print('FCM Token: $token');
// Handle foreground notifications
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('Foreground notification: ${message.messageId}');
handleForegroundNotification(message);
});
// Handle notification that opened app from background
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
print('Notification opened app: ${message.messageId}');
handleBackgroundNotification(message);
});
// Check for notification that launched the app
RemoteMessage? initialMessage = await _firebaseMessaging.getInitialMessage();
if (initialMessage != null) {
print('App launched from notification: ${initialMessage.messageId}');
handleTerminatedNotification(initialMessage);
}
}
void handleForegroundNotification(RemoteMessage message) {
final title = message.notification?.title ?? '';
final body = message.notification?.body ?? '';
final data = message.data;
print('Foreground - Title: $title, Body: $body');
// Pass notification data to Singular
if (data.isNotEmpty) {
Singular.handlePushNotification(data);
}
// Display notification to user
showNotificationDialog(title, body, data);
}
void handleBackgroundNotification(RemoteMessage message) {
print('Processing background notification');
// Pass notification data to Singular
if (message.data.isNotEmpty) {
Singular.handlePushNotification(message.data);
}
// Navigate based on notification data
navigateFromNotification(message.data);
}
void handleTerminatedNotification(RemoteMessage message) {
print('Processing terminated state notification');
// Pass notification data to Singular
if (message.data.isNotEmpty) {
Singular.handlePushNotification(message.data);
}
// Navigate based on notification data
navigateFromNotification(message.data);
}
void showNotificationDialog(
String title,
String body,
Map<String, dynamic> data
) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(body),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
navigateFromNotification(data);
},
child: Text('Open'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Dismiss'),
),
],
),
);
}
void navigateFromNotification(Map<String, dynamic> data) {
// Parse notification data for routing
final route = data['route'];
final productId = data['product_id'];
print('Navigating to: $route');
// Navigate to appropriate screen
if (route == 'product' && productId != null) {
navigatorKey.currentState?.pushNamed('/product/$productId');
} else if (route == 'promo') {
navigatorKey.currentState?.pushNamed('/promo');
} else {
navigatorKey.currentState?.pushNamed('/');
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
navigatorKey: navigatorKey,
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/product': (context) => ProductScreen(),
'/promo': (context) => PromoScreen(),
},
);
}
}
// Placeholder screens
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: Center(child: Text('Home Screen')),
);
}
}
class ProductScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Product')),
body: Center(child: Text('Product Screen')),
);
}
}
class PromoScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Promo')),
body: Center(child: Text('Promo Screen')),
);
}
}
重要な考慮事項
実装上の注意
-
コールバックハンドラなし:
singularLinksHandlerとは異なり、プッシュ通知機能はペイロードコールバックを提供しません。独自のディープリンクロジックを実装して、ユーザーをアプリ内の特定のコンテンツに誘導してください。 - アトリビューションフロー:ユーザーが通知をタップすると、Singularはペイロードを取得し、SDKの初期化によってトリガーされるセッション開始イベントに含めます。バックエンドはこのデータを処理し、プッシュ通知のタッチポイントをアトリビューションし、リエンゲージメント・トラッキングを登録します。
-
ドメインの制限デフォルトでは、[Manage Links(リンクの管理)]ページからの単一リンクドメイン(
sng.link)のみが許可されます。ラップリンク用のESPドメインは、espDomainsを使用して明示的に設定します。 - プラットフォームの違い:iOSでは終了状態のためにAppDelegateの設定が必要ですが、Androidではブリッジモジュールによって自動的に処理されます。
- テスト:開発中にSDKロギングを有効にして、プッシュ通知データが正しくキャプチャされ、処理されていることを確認する。
成功:これらのステップに従うことで、あなたのアプリはSingularでプッシュ通知インタラクションを追跡し、キャンペーンパフォーマンスの洞察を向上させ、正確なリエンゲージメントアトリビューションを保証します。