プッシュ通知(Android)¶
プッシュ通知の設定¶
FCMの設定 (Android) を参照し、設定してください。
Firebase SDKを導入する¶
FCMの設定 (Android) でダウンロードした google-services.json
をアプリモジュールのディレクトリにコピーし、プロジェクトのbuild.gradleに以下を追加してください。
buildscript {
...
dependencies {
...
classpath 'com.google.gms:google-services:4.3.4'
}
}
allprojects {
...
repositories {
...
google()
}
}
アプリのbuild.gradleに以下を追加してください。
dependencies {
implementation 'com.google.firebase:firebase-messaging:20.3.0'
}
apply plugin: 'com.google.gms.google-services'
// This part does not need to be implemented in a Flutter app.
警告
firebase-messagingのバージョン17.1.0以上を指定してください。17.1.0未満は、Repro SDKでRegistration IDを取得できません。
注釈
Gradle 3.4 未満のバージョンの場合は 'implementation' が利用できません。 'compile'を利用してください。
AndroidManifest.xml
ファイルを編集する¶
Receiverの登録¶
以下のXML中の "YOUR_PACKAGE_NAME" をアプリケーションのパッケージ名に置き換え、AndroidManifest.xmlの <application>
タグの中に追加してください。
<receiver
android:name="io.repro.android.ReproReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="YOUR_PACKAGE_NAME" />
</intent-filter>
</receiver>
通知チャンネルの設定¶
Android Oにて、ユーザーが通知を管理できるように通知チャンネルが導入されました。
Repro SDKで利用する通知チャンネルの設定を行うには、「ID」「名前」「ユーザー向けの説明」「badgeの表示有無」を指定する必要があります。
以下のXML中の value
, resource
を設定したい値に書き換え、AndroidManifest.xmlの <application>
タグの中に追加してください。
IDと名前の指定は必須、その他は省略可能です。「ユーザー向けの説明」省略時は空文字列、「badgeの表示有無」省略時はbadgeの表示有りとなります。
警告
- アプリのtargetSDKVersionが26以上かつAndroid O以降の機種で動作させる場合、IDと名前が指定されていなければSDKはReproからのプッシュ通知を表示いたしませんのでご注意ください。
ChannelId
に指定する値は、整数や小数ではなくtest ID
のような文字列を指定してください。文字列以外の値を指定するとチャンネルIDの指定がうまく動作せず、プッシュ通知が表示されません。ChannelName
とChannelDescription
の設定はandroid:resource
属性を使用してください。
<meta-data
android:name="io.repro.android.PushNotification.ChannelId"
android:value="YOUR_CHANNEL_ID">
</meta-data>
<meta-data
android:name="io.repro.android.PushNotification.ChannelName"
android:resource="@string/YOUR_CHANNEL_NAME">
</meta-data>
<meta-data
android:name="io.repro.android.PushNotification.ChannelDescription"
android:resource="@string/YOUR_CHANNEL_DESCRIPTION">
</meta-data>
<meta-data
android:name="io.repro.android.PushNotification.ShowBadge"
android:value="true">
</meta-data>
指定されたIDの通知チャンネルが存在しない場合はAndroidManifest.xmlの指定を基にSDKが自動で作成し、指定されたIDの通知チャンネルが存在する場合は既存のものを利用します。
既存のものを利用する場合、SDKは「名前」、「ユーザー向けの説明」及び「チャンネルの重要度」を更新します。
また、アプリのtargetSDKVersionが25以下の場合、あるいは、Android O未満の機種の場合は、SDKは上記の通知チャンネルに関する設定を無視します。
注釈
通知チャンネルはAndroid Oで導入されたAndroidの標準機能です。通知チャンネルの詳細は こちら をご覧ください。
アイコンと背景色のカスタマイズ¶
Android5.0以降の機種で、通知エリアに表示されるアイコンとその背景色をカスタマイズする場合は、以下のXMLをAndroidManifest.xmlの <application>
タグの中に追加してください。Android5.0未満の機種では以下の設定は無視され、通知エリアにはアプリケーションのアイコンおよびシステムデフォルトの背景色が使用されます。
<meta-data
android:name="io.repro.android.PushNotification.SmallIcon"
android:resource="@drawable/YOUR_ICON_ID">
</meta-data>
<meta-data
android:name="io.repro.android.PushNotification.AccentColor"
android:resource="@color/YOUR_COLOR_ID">
</meta-data>
Registration IDをReproに送信¶
警告
AndroidManifest.xml
に 通知権限(POST_NOTIFICATIONS) を追加する- 通知権限を求める ダイアログ を実装する
Reproの setup()
を呼び出した直後に、 Repro.enablePushNotification
を呼び出してください。
import io.repro.android.Repro;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
...
Repro.setup(this, "YOUR_APP_TOKEN");
Repro.enablePushNotification();
...
}
}
// This section should be implemented in Java, even for Flutter applications.
FirebaseMessagingService
を継承したクラスの onNewToken
にて Repro.setPushRegistrationID
を呼び出してください。
import io.repro.android.Repro;
public class MyFirebaseMessagingService extends FirebaseMessagingService {
...
@Override
public void onNewToken(String token) {
Repro.setPushRegistrationID(token);
}
...
}
// main.dart
FirebaseMessaging messaging = FirebaseMessaging.instance;
messaging.onTokenRefresh.listen((token) async {
await Repro.setPushRegistrationID(token);
});
以下をAndroidManifest.xmlの <application>
タグの中に追加してください。
<service
android:name=".MyFirebaseMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
// This part does not need to be implemented in a Flutter app.
注釈
AndroidManifest.xmlに複数のFirebaseMessagingServiceを追加しても、その内の一つのFirebaseMessagingServiceしか動作しません。
このため、既にFirebaseMessagingServiceを継承したクラスを実装している場合は、そのクラスの onNewToken
にて Repro.setPushRegistrationID
を呼び出すようにし、新たにFirebaseMessagingServiceを継承したクラスを作成しないでください。
上記の実装が終わったら、 プッシュ通知作成画面 をご覧ください。
オプション:プッシュ通知受信時の動作をカスタマイズする¶
プッシュ通知受信時の動作をカスタマイズしたい場合は、FirebaseMessagingServiceのonMessageReceivedメソッドを実装してください。
FirebaseMessagingServiceのonMessageReceivedを実装する¶
onMessageReceived
をオーバーライドして、以下の3つのメソッドを呼び出してください。
Repro.applicationShouldHandlePushNotification
を呼び出して、受信したメッセージをアプリケーションで処理すべきか判定してください。このメソッドがfalse
を返す場合、そのメッセージはSDKで処理されます。Repro.isAlreadyReceivedPushNotification
を呼び出して、受信済みのメッセージか判定してください。アプリケーションのアンインストール・再インストールを繰り返した場合、重複したメッセージが届く場合があります。Repro.markPushNotificationReceived
を呼び出して、受信済みのメッセージであることを記録してください。
注釈
スタンダード形式の通知を受信するためには ReproReceiver
が必要です。AndroidManifest.xmlに登録されている ReproReceiver
を削除しないでください。
@Override
public void onMessageReceived(RemoteMessage message) {
Map<String, String> data = message.getData();
// check whether the application should handle this push notification.
if (!Repro.applicationShouldHandlePushNotification(this, data)) {
Log.d(TAG, "Ignore push notification: it will be handled by SDK: " + data.toString());
return;
}
// check whether this push notification is already received.
if (Repro.isAlreadyReceivedPushNotification(this, data)) {
Log.d(TAG, "Ignore push notification: it is already received: " + data.toString());
return;
}
// mark this push notification as "received".
Log.d(TAG, "Mark push notification as received: " + data.toString());
Repro.markPushNotificationReceived(this, data);
// Implement procedures unique to the application here
...
}
// main.dart
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
// check whether the application should handle this push notification.
final shouldHandle = await Repro.applicationShouldHandlePushNotification(message.data);
if (!shouldHandle) {
debugPrint("Ignore push notification: it will be handled by SDK: ${message.data.toString()}");
return;
}
// check whether this push notification is already received.
final isReceived = await Repro.isAlreadyReceivedPushNotification(message.data);
if (isReceived) {
debugPrint("Ignore push notification: it is already received: ${message.data.toString()}");
return;
}
// mark this push notification as "received".
debugPrint("Mark push notification as received: ${message.data.toString()}");
await Repro.markPushNotificationReceived(message.data);
// Implement procedures unique to the application here
...
});
開封された通知をトラッキングする¶
スタンダード形式のメッセージの場合はSDKが自動で開封をトラッキングします。JSON形式のメッセージの場合は、アプリケーションから Repro.trackNotificationOpened
を呼び出してトラッキングする必要があります。
以下では、JSON形式のメッセージからActivityを起動する通知を作成し、起動されたActivity側で Repro.trackNotificationOpened
を呼び出すサンプルを示します。
public class MyFirebaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "MessagingService";
private static final String CHANNEL_ID = "custom-firebase-messaging-service-channel";
private final Random mRandom = new Random();
@Override
public void onNewToken(String token) {
Repro.setPushRegistrationID(token);
}
@Override
public void onMessageReceived(RemoteMessage message) {
final int identifier = mRandom.nextInt();
Map<String, String> data = message.getData();
// check whether the application should handle this push notification.
if (!Repro.applicationShouldHandlePushNotification(this, data)) {
Log.d(TAG, "Ignore push notification: it will be handled by SDK: " + data.toString());
return;
}
// check whether this push notification is already received.
if (Repro.isAlreadyReceivedPushNotification(this, data)) {
Log.d(TAG, "Ignore push notification: it is already received: " + data.toString());
return;
}
// mark this push notification as "received".
Log.d(TAG, "Mark push notification as received: " + data.toString());
Repro.markPushNotificationReceived(this, data);
String messageText;
if (data.containsKey("message")) {
messageText = data.get("message");
} else {
messageText = "default message";
}
final Notification.Builder builder = new Notification.Builder(this)
.setContentTitle(this.getResources().getString(R.string.app_name))
.setContentText(messageText)
.setSmallIcon(R.mipmap.ic_launcher_round)
.setAutoCancel(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createOrUpdateChannel();
builder.setChannelId(CHANNEL_ID);
}
Intent resultIntent = new Intent(this, SomeActivity.class);
String notificationIdKey = data.get(io.repro.android.ReproReceiver.NOTIFICATION_ID_KEY);
if (notificationIdKey != null) {
resultIntent.putExtra(io.repro.android.ReproReceiver.NOTIFICATION_ID_KEY, notificationIdKey);
}
PendingIntent resultPendingIntent = PendingIntent.getActivity(this, identifier, resultIntent, PendingIntent.FLAG_CANCEL_CURRENT);
builder.setContentIntent(resultPendingIntent);
final NotificationManager notificationManager = (NotificationManager)this.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(identifier, builder.getNotification());
}
@TargetApi(Build.VERSION_CODES.O)
private void createOrUpdateChannel() {
NotificationChannel newChannel = new NotificationChannel(CHANNEL_ID, "Custom Channel", NotificationManager.IMPORTANCE_DEFAULT);
newChannel.setDescription("");
newChannel.setShowBadge(true);
// create or update the Notification channel
final NotificationManager notificationManager = (NotificationManager)this.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(newChannel);
}
}
// This is a sample that tracks the opening of a message in JSON format.
// For more detailed implementations, see the sample code for the Java version.
// main.dart
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
// mark this push notification as "received".
debugPrint("Mark push notification as received: ${message.data.toString()});
await Repro.markPushNotificationReceived(message.data);
...
});
起動されたActivity側では、上述の resultIntent
にセットしたExtrasからプッシュ通知IDを取得し、 onResume
で Repro.trackNotificationOpened
にセットしてください。なおアクティビティのローンチモードが singleTop
の場合は、 onNewIntent
で同様にプッシュ通知IDをセットしてください。
public class SomeActivity extends Activity {
@Override
protected void onResume() {
super.onResume();
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(io.repro.android.ReproReceiver.NOTIFICATION_ID_KEY)) {
Repro.trackNotificationOpened(extras.getString(io.repro.android.ReproReceiver.NOTIFICATION_ID_KEY));
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(io.repro.android.ReproReceiver.NOTIFICATION_ID_KEY)) {
Repro.trackNotificationOpened(extras.getString(io.repro.android.ReproReceiver.NOTIFICATION_ID_KEY));
}
this.setIntent(intent);
}
// There is no sample code for Flutter in this section.
注釈
- プッシュ通知IDは、受信したメッセージのdataから
io.repro.android.ReproReceiver.NOTIFICATION_ID_KEY
というキーで取得できます。
特定のアクティビティの起動時にセッションを開始しないようにする¶
プッシュ通知の受信を切っ掛けにしてアクティビティを自動起動すると効果測定の結果に影響がある場合など、Reproとのセッションの開始を抑制したい場合があります。
この際には、アクティビティの起動時に使用する Intent
の Extras
に ___repro_prevent_session_start___: true
のフラグを設定してください。
public class MyFirebaseMessagingService extends FirebaseMessagingService {
...
@Override
public void onMessageReceived(RemoteMessage message) {
...
// The existing code
Intent intent = new Intent(myContext, ActivityStartedByPushNotification.class)
// Add this code here
intent.putExtra("___repro_prevent_session_start___", true);
// The existing code
startActivity(intent);
}
}
// This implementation is not possible in the Dart language.
このフラグを設定することで、アクティビティの起動時にReproとのセッションが開始されなくなります。 起動されたアクティビティから他のアクティビティに遷移する場合やこれ以外のアクティビティの起動時においては、改めてフラグが設定されない限りセッションは開始されます。
警告
この設定を行うには、アプリケーションに対し Android SDK 5.6.1 以上の導入が必要です。
オプション:アプリリンクを使用する¶
端末に表示されたプッシュ通知およびアプリ内メッセージに設定されたリンクをタップした際に実行されるURLには、ユニバーサルリンク(またはアプリリンクと呼ばれる)を指定することが可能です。 (簡単のため、以下ではこれらをユニバーサルリンクと呼びます)
アプリにてユニバーサルリンクの実行時の処理を追加するには、以下の通り実装を行う必要があります。 (ここで設定された動作は、プッシュ通知およびアプリ内メッセージ対し有効となります。)
ユニバーサルリンクのURLパターンを登録する¶
ユニバーサルリンクを利用するためには、まずユニバーサルリンクのURLのパターン(正規表現)を指定する必要があります。
注釈
パターン(正規表現)に合致すると、プッシュ通知の開封時や、アプリ内メッセージの遷移先に設定されたURLに対して、後述のコールバック処理をキックします。
また、アプリ内メッセージでHTMLコンテンツを表示するとき、そのコンテンツ内で発生したリクエストに対してもユニバーサルリンクの判定が行われます。
この指定は、iOSの場合は AppDelegate
または .plist
ファイル、Androidの場合は Application
継承クラス または AndroidManifest.xml
ファイルのどちらか一方に記述することで行うことができます。
// AppDelegate.m
#import <Repro/Repro.h>
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[Repro addOpenUrlFilterRegEx:@"https://example\\.com/page/"];
[Repro addOpenUrlFilterRegEx:@"your-domain\\.com/.+\?universal=true"];
...
}
// .plist file
/*
<dict>
<key>RPROpenUrlFilterRegExList</key>
<array>
<string>https://example\.com/page/</string>
<string>your-domain\.com/.+\?universal=true</string>
</array>
...
</dict>
*/
// MyApplication.java
import io.repro.android.Repro;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Repro.addOpenUrlFilterRegEx("https://example\\.com/page/");
Repro.addOpenUrlFilterRegEx("your-domain\\.com/.+\\?universal=true");
...
}
...
}
// AndroidManifest.xml
// To specify multiple URL patterns, enter them separated by commas.
<application>
...
<meta-data
android:name="io.repro.android.OpenUrlFilterRegExList"
android:value="https://example\\.com/page/;your-domain\\.com/.+\\?universal=true">
</meta-data>
</application>
// AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
Repro.add(openUrlFilterRegEx: "https://example\\.com/page/")
Repro.add(openUrlFilterRegEx: "your-domain\\.com/.+\\?universal=true")
...
}
// .plist file
/*
<dict>
<key>RPROpenUrlFilterRegExList</key>
<array>
<string>https://example\.com/page/</string>
<string>your-domain\.com/.+\?universal=true</string>
</array>
...
</dict>
*/
Please follow native iOS and Android instructions in order to add the filters
to the plist/AndroidManifest files or the main AppDelegate/Application classes.
Please follow native iOS and Android instructions in order to add the filters
to the plist/AndroidManifest files or the main AppDelegate/Application classes.
// YourAppNameScene.cpp
bool YourAppName::init() {
ReproCpp::addOpenUrlFilterRegEx("https://example\\.com/page/");
ReproCpp::addOpenUrlFilterRegEx("your-domain\\.com/.+\\?universal=true");
...
}
Please follow native iOS and Android instructions in order to add the filters
to the plist/AndroidManifest files or the main AppDelegate/Application classes.
Please follow native iOS and Android instructions in order to add the filters
to the plist/AndroidManifest files or the main AppDelegate/Application classes.
警告
ユニバーサルリンクとして設定するURLパターンは、可能な限り ドメインとパスまで含めて具体的に指定 し、 全て小文字で指定 してください。
もし、 https のようにスキームのみを指定した場合、アプリ内メッセージで表示された画面内で実行されたスクリプトなどによるリクエストもユニバーサルリンクと誤って判定され、意図しないタイミングでコールバック処理が実行される可能性があります。
また、URLパターンのスキーム/ドメイン部分に大文字を使用した場合、HTMLアプリ内メッセージで表示したボタンからユニバーサルリンクを使って遷移させようとするときに、スキーム/ドメイン部分が自動的に小文字に変換されて処理されます。パターン(正規表現)に一致せず、意図したタイミングでコールバック処理が実行されない可能性があります。
ユニバーサルリンク実行時の処理を登録する¶
次に、ユニバーサルリンクが実行された際に動作するコールバック処理を実装します。 処理内には、例えばURLのパターンに応じて画面遷移を行う動作などを記述します。
#import <Repro/Repro.h>
...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[Repro setOpenUrlCallback:^(NSURL *url) {
if ([url.host isEqualToString:@"example.com"]) {
// In case of your universal link perform navigation, present content, ...
handleUniversalLink(url);
}
}];
...
[Repro setup:@"YOUR_APP_TOKEN"];
...
}
// Set a callback that is executed everytime an URL is about to be opened
Repro.setOpenUrlCallback { url in
if url.host == "example.com" {
// In case of your universal link perform navigation, present content, ...
handleUniversalLink(url)
}
}
...
Repro.setup(token: "YOUR_APP_TOKEN")
...
import io.repro.android.Repro;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// In order to execute a callback when a push notification is tapped,
// the callback must be set in Application, not Activity.
Repro.setOpenUrlCallback(new Repro.OpenUrlCallback() {
// ** This callback method is executed in the main thread **
@Override
public void onOpened(Uri uri) {
if ("example.com".equals(uri.getHost())) {
// In case of your universal link perform navigation, present content, ...
}
}
});
...
Repro.setup(this, YOUR_APP_TOKEN);
...
}
...
}
Repro.setOpenUrlCallback((uri) { // uri is of type Uri
debugPrint("Universal Link Callback Handler received: " + uri.toString());
});
// Somewhere before Repro.Setup(...)
Repro.SetOpenUrlCallback(uri => // uri is of type Uri
{
Debug.Log("Universal Link Callback Handler received: " + uri.ToString());
});
bool YourAppName::init() {
ReproCpp::addOpenUrlFilterRegEx(...);
ReproCpp::setOpenUrlCallback([](const char *url) {
if (strcmp(url, "example.com") == 0) {
// In case of your universal link perform navigation, present content, ...
}
});
ReproCpp::setup("YOUR_APP_TOKEN")
...
}
Repro.setOpenUrlCallback(function(url) { // url is of type String
alert('Universal Link Callback Handler received: ' + url);
if (url.includes("http://example.org")) {
// In case of your universal link perform navigation, present content, ...
}
});
Repro.setOpenUrlCallback( (url) => { // url is of type String
console.log('Universal Link Callback Handler received: ' + url);
if (url.includes("http://example.org")) {
// In case of your universal link perform navigation, present content, ...
}
});