Push Notification for Android¶
Settings for Push Notification¶
See Settings for FCM (Android) to setup.
Add Firebase SDK¶
Copy google-services.json
which you have downloaded from Settings for FCM (Android) to the app directory, and add the lines below to the project level build.gradle file.
buildscript {
...
dependencies {
...
classpath 'com.google.gms:google-services:4.3.4'
}
}
allprojects {
...
repositories {
...
google()
}
}
Add the following to your application's 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.
Warning
Please specify version 17.1.0 or above for firebase-messaging. Repro SDK cannot retrieve Registration ID with versions below 17.1.0.
Note
'implementation' is not available for versions below Gradle 3.4. Please use 'compile' instead.
Edit AndroidManifest.xml
¶
Register receiver¶
Replace "YOUR_PACKAGE_NAME" with your application's package name in the following snippet, and add it to your AndroidManifest.xml inside of <application>
tag.
<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>
Setup Notification Channels¶
Since Android O, Notification Channels were introduced to help users manage their notifications.
To set the notification channels in Repro SDK, please specify the following: ID, channel name, description for users, and with or without badge display.Replace the value
, resource
within the XML below with the desired value, and add the <application>
tag into the AndroidManifest.xml.
Warning
If the targetSDKVersion of your application is greater than or equal to 26, please note that channel id and name are required or the SDK won't show notifications sent from Repro while running on devices with Android O or later.
The
ChannelId
must be a string, liketest ID
, instead of an integer or decimal. If you specify a value other than a string, channel IDs will not work and push notifications won't be displayed.The
ChannelName
andChannelDescription
must be configured using theandroid:resource
attribute.
<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>
If the channel of the specified id does not exist, it will be created automatically by the SDK based on the information provided in AndroidManifest.xml. If there is an existing channel with the specified id, the SDK will use it.
The SDK will update the channel name and description and channel importance of the existing channel being used by the SDK.
The SDK will ignore the settings above when the targetSDKVersion of your application is 25 or below, or when the application is running on devices with versions earlier than Android O.
Note
Notification Channels were introduced as a standard feature in Android O. For the details, please refer to this page.
Customize Icon and Background Color¶
When customizing the icon and the background color displayed in the notification area for devices with Android 5.0 or above, add the XML construct below inside of the <application>
tag in your AndroidManifest.xml. This setting will be ignored with devices under Android 5.0, and the notification area will use the application's icon as well as the system's default background color.
<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>
Send Registration ID to Repro¶
Right after calling setup()
, call 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.
Call Repro.setPushRegistrationID
in the method onNewToken
inherited from FirebaseMessagingService
.
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);
});
Add the following snippet to your AndroidManifest.xml inside of <application>
tag.
<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.
Note
Adding multiple FirebaseMessagingService to AndroidManifest.xml will result in only one of the FirebaseMessagingServices actually working.
Therefore, if you have already implemented a class inheriting FirebaseMessagingService, please call Repro.setPushRegistrationID
in the onNewToken
of the existing class, rather than creating a new class inheriting FirebaseMessagingService.
After finishing the above implementation, see Create Push Notification.
Option: customize the behavior when receiving messages¶
If you want to customize the behavior when receiving push notifications, please implement the onMessageReceived method of FirebaseMessagingService.
Implement onMessageReceived of FirebaseMessagingService¶
Override onMessageReceived
and call the following three methods.
Call
Repro.applicationShouldHandlePushNotification
and decide whether to use the received message in the application or not. If this method returnsfalse
, the message will be processed by the SDK.Call
Repro.isAlreadyReceivedPushNotification
and detect the message has already been received or not. The device may receive duplicated messages if the application is being uninstalled and re-installed repeatedly.Call
Repro.markPushNotificationReceived
and mark the message received.
Note
ReproReceiver
is required to receive standard format messages. Do not delete 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
...
});
Track notification as opened¶
Standard format message opening will be tracked automatically by the Repro SDK. For JSON formatted messages, you will need to call Repro.trackNotificationOpened
to track message opens.
The below is a sample of creating notification that launches Activity from the JSON format message, and call Repro.trackNotificationOpened
within the called Activity.
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);
...
});
Within the launched Activity, set the push notification ID from the Extras of the previous resultIntent
to Repro.trackNotificationOpened
. If the launch mode of the activity is singleTop
, set the push notification the same way with onNewIntent
.
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.
Note
The push notification ID can be retrieved from the Bundle of the received message with the
io.repro.android.ReproReceiver.NOTIFICATION_ID_KEY
key.
How to prevent specific activities from starting a Repro session¶
For example activities launched by push notifications are a good example for a cases where preventing certain activities to start a Repro session with the server backend could be advantageous. In such cases, you should set the flag ___repro_prevent_session_start___: true
in the Extras
of the Intent
used to launch the activity.
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.
By setting this flag, you can prevent the start of a session with the Repro server at startup. When transitioning from a launched activity to another, the session will be started unless the flag is set again.
Warning
To apply this setting, you need to install Android SDK 5.6.1 or higher for your application.
Option: Using App-Links¶
It is possible to custom handle the opening of Universal-Links (also called App-Links) when Push Notifications or In-App Messages are tabbed. (For simplification from now only called Universal-Links.)
Please follow the examples below in order to add a custom Universal-Link callback handler which is executed every time an Universal-Link would be opened from a Push-Notification or In-App Message.
How to register a regular expression to match Universal-Links.¶
In order to use Universal-Link callbacks, a regular expression based url matcher must be added. For iOS apps, these matcher regular expressions can be added in the AppDelegate
or in the Apps designated .plist
file. For Android apps your class that extents Application
can be used or if that is not possible, the regular expressions can also be added to your AndroidManifest.xml
file.
// 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.
Adding a callback handler for opening Universal-Links¶
Next a callback handler for custom processing of Universal-Links is added. The URL that was about to be opened is passed to the callback, where then depending on the URL custom code can be executed.
#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, ...
}
});