SDK

Remote Notifications

In this page you'll learn how notifications are handled in your app and what are all the options at your disposal to create a great messaging experience for your users.

Notificare supports several types of interactive and actionable notifications that will be handled for you without any extra development. If you are going to prevent this default behaviour, please note that you will have to either handle all the functionality yourself (metrics logging, presenting UI or collect replies) or if you don't, you understand that some features will not work as advertised.

Notification channels

Starting in Android 8 (a.k.a Oreo), in order for your app to display notifications, you need to create channels. Without a channel, notifications will never appear in the notification manager. By default, we create a single channel.

Every notification will appear in the default channel "Push Notifications". You can change the name and the description of the default channel by adding these values in your app/res/strings.xml file:

<string name="notificare_default_channel_name">Push Notifications</string>
<string name="notificare_default_channel_description">This channel shows push notifications</string>

You can also create channels yourself in the app. If you want a channel that your app created to be the default channel, add the following metadata tag to your Manifest.xml:

<meta-data
    android:name="re.notifica.push.default_channel_id"
    android:value="my_custom_channel_id" />

If you want to prevent Notificare from creating the default channel automatically, you can add the follow metadata tag to your Manifest.xml.

<meta-data
    android:name="re.notifica.push.automatic_default_channel_enabled"
    android:value="false" />

Enabling Notifications

In order to enable the device to receive notifications, all that you need to do is invoke a single method.

NotificarePush.enableRemoteNotifications()
NotificarePush.INSTANCE.enableRemoteNotifications();

Typically, the step above is done during some form of user onboarding. When the user already went through that flow, we can automatically enable notifications when the onReady event is triggered, be that via a custom NotificareIntentReceiver or a Notificare.OnReadyListener.

override fun onReady(application: NotificareApplication) {
    // Check if the user has previously enabled remote notifications.
    if (NotificarePush.isRemoteNotificationsEnabled) {
        NotificarePush.enableRemoteNotifications()
    }
}
@Override
public void onReady(@NotNull NotificareApplication application) {
    // Check if the user has previously enabled remote notifications.
    if (NotificarePush.INSTANCE.isRemoteNotificationsEnabled()) {
        NotificarePush.INSTANCE.enableRemoteNotifications();
    }
}

As shown above, you can check if the user has previously enabled remote notifications. Additionally, you can check if the user has disabled notifications in the System Settings.

NotificarePush.allowedUI
NotificarePush.INSTANCE.getAllowedUI();

If you want to also give access to your app's settings view from the notification settings in the device's settings, you can listen to the NOTIFICATION_PREFERENCES intent. This will add a link to your activity inside the device's settings.

<activity android:name=".SettingsActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.NOTIFICATION_PREFERENCES" />
    </intent-filter>
</activity>

notification settings link

Receiving Notifications

Due to the changes in Android 12 when it comes to handling notifications, your Activity needs to become the trampoline. Assuming you will let your MainActivity receive the notification intents, you need to declare the following intent-filter it in your Manifest.xml.

<activity android:name=".MainActivity" >
    <intent-filter>
        <action android:name="re.notifica.intent.action.RemoteMessageOpened" />

        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

Additionally, in order to process those intents, you need to take care of them in your MainActivity as shown below.

override fun onCreate(savedInstanceState: Bundle?) {
    // more code ...

    if (intent != null) handleIntent(intent)
}

override fun onNewIntent(intent: Intent?) {
    // more code ...

    if (intent != null) handleIntent(intent)
}

private fun handleIntent(intent: Intent) {
    if (NotificarePush.handleTrampolineIntent(intent)) {
        Log.d(TAG, "Trampoline intent handled.")
        return
    }

    // more code ...
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // more code ...

    Intent intent = getIntent();
    if (intent != null) handleIntent(intent);
}

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    // more code ...

    if (intent != null) handleIntent(intent);
}

private void handleIntent(@NonNull Intent intent) {
    if (NotificarePush.INSTANCE.handleTrampolineIntent(intent)) {
        Log.d(TAG, "Test device intent handled.");
        return;
    }

    // more code ...
}

Listening to Received Notifications

Once you're receiving notifications in your app, we can dive deeper and fully understand how they are handled. Our library takes care of placing a notification in the Notification Center so developers will not need to take any action in order to display notifications. If you want to be notified incoming notifications in your own Intent Receiver, for example to add a badge to your application launcher icon, you can leverage the NotificarePushIntentReceiver.

Start by creating a subclass of that Intent Receiver and overriding the onNotificationReceived() method.

class CustomPushIntentReceiver: NotificarePushIntentReceiver() {
    override fun onNotificationReceived(notification: NotificareNotification) {
        // more code ...
    }
}
class CustomPushIntentReceiver extends NotificarePushIntentReceiver {
    @Override
    protected void onNotificationReceived(@NotNull NotificareNotification notification) {
        // more code ...
    }
}

In order to receive those intents in your Intent Receiver you need to let Notificare know about your class.

NotificarePush.intentReceiver = CustomPushIntentReceiver::class.java
NotificarePush.INSTANCE.setIntentReceiver(CustomPushIntentReceiver.class);

Lastly, declare your custom intent receiver in your Manifest.xml.

<receiver
    android:name=".CustomPushIntentReceiver"
    android:exported="false" />

Presenting notifications

A notification can be opened by either tapping the actual notification or by tapping an action inside the notification. We will emit an intent in each case to an Activity that declares the appropriate intent-filters. To receive those intents, add the following to your MainActivity in the Manifest.xml.

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="re.notifica.intent.action.NotificationOpened" />

        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    <intent-filter>
        <action android:name="re.notifica.intent.action.ActionOpened" />

        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

To handle the intents above, you can take the managed approach and use our NotificarePushUI module which takes care of all the events and UI types as well as actions, or you can fully take over and present them however you prefer. However, be aware if you take the non-managed approach as you will have to deal with all aspects and types of presenting the notifications, including the events needed to show the user's engagement in our Dashboard.

The code below illustrates how this works when using the managed approach.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // more code ...

    if (intent != null) handleIntent(intent)
}

override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    // more code ...

    if (intent != null) handleIntent(intent)
}

private fun handleIntent(intent: Intent) {
    when (intent.action) {
        NotificarePush.INTENT_ACTION_NOTIFICATION_OPENED -> {
            val notification: NotificareNotification = requireNotNull(
                intent.getParcelableExtra(Notificare.INTENT_EXTRA_NOTIFICATION)
            )

            NotificarePushUI.presentNotification(this, notification)
            return
        }
        NotificarePush.INTENT_ACTION_ACTION_OPENED -> {
            val notification: NotificareNotification = requireNotNull(
                intent.getParcelableExtra(Notificare.INTENT_EXTRA_NOTIFICATION)
            )

            val action: NotificareNotification.Action = requireNotNull(
                intent.getParcelableExtra(Notificare.INTENT_EXTRA_ACTION)
            )

            NotificarePushUI.presentAction(this, notification, action)
            return
        }
    }

    // more code ...
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // more code ...

    Intent intent = getIntent();
    if (intent != null) handleIntent(intent);
}

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    // more code ...

    if (intent != null) handleIntent(intent);
}

private void handleIntent(@NonNull Intent intent) {
    switch (intent.getAction()) {
        case NotificarePush.INTENT_ACTION_NOTIFICATION_OPENED: {
            NotificareNotification notification = intent.getParcelableExtra(Notificare.INTENT_EXTRA_NOTIFICATION);

            NotificarePushUI.INSTANCE.presentNotification(this, notification);
        }
        case NotificarePush.INTENT_ACTION_ACTION_OPENED: {
            NotificareNotification notification = intent.getParcelableExtra(Notificare.INTENT_EXTRA_NOTIFICATION);
            NotificareNotification.Action action = intent.getParcelableExtra(Notificare.INTENT_EXTRA_ACTION);

            NotificarePushUI.INSTANCE.presentAction(this, notification, action);
        }
    }

    // more code ...
}

Additionally, when using the managed approach, you can listen to Notification lifecycle events and perform any additional steps you may require. Let your Activity implement the NotificarePushUI.NotificationLifecycleListener and add following methods as needed.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // more code ...

    NotificarePushUI.addLifecycleListener(this)
}

override fun onDestroy() {
    super.onDestroy()
    // more code ...

    NotificarePushUI.removeLifecycleListener(this)
}

override fun onNotificationWillPresent(notification: NotificareNotification) {

}

override fun onNotificationPresented(notification: NotificareNotification) {

}

override fun onNotificationFinishedPresenting(notification: NotificareNotification) {

}

override fun onNotificationFailedToPresent(notification: NotificareNotification) {

}

override fun onNotificationUrlClicked(notification: NotificareNotification, uri: Uri) {

}

override fun onActionWillExecute(notification: NotificareNotification, action: NotificareNotification.Action) {

}

override fun onActionExecuted(notification: NotificareNotification, action: NotificareNotification.Action) {

}

override fun onActionFailedToExecute(
    notification: NotificareNotification,
    action: NotificareNotification.Action,
    error: Exception?
) {

}

override fun onCustomActionReceived(uri: Uri) {

}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // more code ...

    NotificarePushUI.INSTANCE.addLifecycleListener(this);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    // more code ...

    NotificarePushUI.INSTANCE.removeLifecycleListener(this);
}

@Override
public void onNotificationWillPresent(@NotNull NotificareNotification notification) {

}

@Override
public void onNotificationPresented(@NotNull NotificareNotification notification) {

}

@Override
public void onNotificationFinishedPresenting(@NotNull NotificareNotification notification) {

}

@Override
public void onNotificationFailedToPresent(@NotNull NotificareNotification notification) {

}

@Override
public void onNotificationUrlClicked(@NotNull NotificareNotification notification, @NotNull Uri uri) {

}

@Override
public void onActionWillExecute(@NotNull NotificareNotification notification, @NotNull NotificareNotification.Action action) {

}

@Override
public void onActionExecuted(@NotNull NotificareNotification notification, @NotNull NotificareNotification.Action action) {

}

@Override
public void onActionFailedToExecute(@NotNull NotificareNotification notification, @NotNull NotificareNotification.Action action, @org.jetbrains.annotations.Nullable Exception error) {

}

@Override
public void onCustomActionReceived(@NotNull Uri uri) {

}

In modern apps, this is a great way of creating interactions between notifications, and your own app content, allowing you to send messages that can eventually drive the user to open content hidden deeper in your app.

To prepare your app to handle deep links is not complicated and will allow you to handle not only Deep Link notification types, but also any link made from a web page. In order to indicate your app that you will handle a custom URL scheme you simply have to declare the following in the AndroidManifest.xml and be sure to update the scheme to match yours:

<activity android:name=".DeeplinkActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="com.example" />
    </intent-filter>
</activity>

By simply providing a data entry in your activity's intent-filter element, any click in a link from a web page using com.example://example.com/product?id=1 as the href value, would simply trigger your app to open.

If you are planning to handle Dynamic Links, you must also add all the domain prefixes you've created:

<activity android:name=".DeeplinkActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:host="demo.ntc.re" android:scheme="https"/>
    </intent-filter>
</activity>

But of course you will want to show something to the user based on that URL. That is also easily done by implementing the following in the activity you've declared to use a URL scheme:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // more code ...

    if (intent != null) handleIntent(intent)
}

override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    // more code ...

    if (intent != null) handleIntent(intent)
}

private fun handleIntent(intent: Intent) {
    // more code ...

    if (Notificare.handleDynamicLinkIntent(this, intent)) {
        Log.d(TAG, "Dynamic link handled.")
        return
    }

    val uri = intent.data
    if (uri != null) {
        // Open a view based on the received URI.
    }
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // more code ...

    Intent intent = getIntent();
    if (intent != null) handleIntent(intent);
}

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    // more code ...

    if (intent != null) handleIntent(intent);
}

private void handleIntent(@NonNull Intent intent) {
    // more code ...

    if (Notificare.INSTANCE.handleDynamicLinkIntent(this, intent)) {
        Log.d(TAG, "Dynamic link handled.");
        return;
    }

    Uri uri = intent.getData();
    if (uri != null) {
        // Open a view based on the received URI.
    }
}

If you need to open external deep links in your notifications, you need to add the appropriate entries to your AndroidManifest.xml for those links to work in Android 11, for example to handle HTTP and HTTPS links, you would need to add:

<queries>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https" />
    </intent>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="http" />
    </intent>
</queries>

Another common example where you will make use of deep links, would be to take proper action whenever users click in hypertext links in a Notificare's HTML or Web Page notification type. This is done by first declaring all the URL schemes that you want to handle in your AndroidManifest.xml:

<meta-data
    android:name="re.notifica.push.ui.notification_url_schemes"
    android:resource="@array/notification_url_schemes" />

This entry will require you to create a app/res/values/notification_url_schemes.xml file with the following content:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="notification_url_schemes">
        <item>com.example</item>
        <item>com.example2</item>
        <item>com.example3</item>
    </string-array>
</resources>

Any click in an HTML or Web Page notification type, would be intercepted by our library and trigger the method onNotificationUrlClicked() from NotificarePushUI.NotificationLifecycleListener.

Inbox

With our library it's extremely easy to implement an in-app inbox. Implementing an inbox increases considerably the engagement rate of your notifications simply because messages will always be available inside your app. To activate the inbox functionality, please follow the instructions described here.

After activating this functionality you can start implementing your inbox in any activity or fragment of your app. The inbox is available via the NotificareInbox module. The inbox is exposed as LiveData, so it is really easy to hook up your activities or fragments to a list of inbox items, for example:

NotificareInbox.observableItems.observe(this, { items ->
    // Do something with the items.
})
NotificareInbox.INSTANCE.getObservableItems().observe(this, items -> {
    // Do something with the items.
});

In the same way, you can listen for the number of unread messages.

NotificareInbox.observableBadge.observe(this, { badge ->
    // Do something with the badge.
})
NotificareInbox.INSTANCE.getObservableBadge().observe(this, badge -> {
    // Do something with the badge.
});

If you don't want to use LiveData, you can still get the items and the badge in the inbox.

// Items
NotificareInbox.items

// Badge
NotificareInbox.badge
// Items
NotificareInbox.INSTANCE.getItems();

// Badge
NotificareInbox.INSTANCE.getBadge();

Managing inbox items

Assuming that you hold a reference to an item, to open an inbox item you would simply do something like this:

val item: NotificareInboxItem

NotificareInbox.open(item, object : NotificareCallback<NotificareNotification> {
    override fun onSuccess(result: NotificareNotification) {

    }

    override fun onFailure(e: Exception) {

    }
})
NotificareInboxItem item;

NotificareInbox.INSTANCE.open(item, new NotificareCallback<NotificareNotification>() {
    @Override
    public void onSuccess(NotificareNotification result) {

    }

    @Override
    public void onFailure(@NotNull Exception e) {

    }
});

To remove items from the inbox you would invoke the following method:

val item: NotificareInboxItem

NotificareInbox.remove(item, object : NotificareCallback<Unit> {
    override fun onSuccess(result: Unit) {

    }

    override fun onFailure(e: Exception) {

    }
})
NotificareInboxItem item;

NotificareInbox.INSTANCE.remove(item, new NotificareCallback<Unit>() {
    @Override
    public void onSuccess(Unit result) {

    }

    @Override
    public void onFailure(@NotNull Exception e) {

    }
});

Additionally, you can also mark a message as read by invoking the following method:

val item: NotificareInboxItem

NotificareInbox.markAsRead(item, object : NotificareCallback<Unit> {
    override fun onSuccess(result: Unit) {

    }

    override fun onFailure(e: Exception) {

    }
})
NotificareInboxItem item;

NotificareInbox.INSTANCE.markAsRead(item, new NotificareCallback<Unit>() {
    @Override
    public void onSuccess(Unit result) {

    }

    @Override
    public void onFailure(@NotNull Exception e) {

    }
});

Or mark all messages as read by invoking the following method:

NotificareInbox.markAllAsRead(object : NotificareCallback<Unit> {
    override fun onSuccess(result: Unit) {

    }

    override fun onFailure(e: Exception) {

    }
})
NotificareInbox.INSTANCE.markAllAsRead(new NotificareCallback<Unit>() {
    @Override
    public void onSuccess(Unit result) {

    }

    @Override
    public void onFailure(@NotNull Exception e) {

    }
});

To remove all items in the inbox you would do the following:

NotificareInbox.clear(object : NotificareCallback<Unit> {
    override fun onSuccess(result: Unit) {

    }

    override fun onFailure(e: Exception) {

    }
})
NotificareInboxItem item;

NotificareInbox.INSTANCE.clear(new NotificareCallback<Unit>() {
    @Override
    public void onSuccess(Unit result) {

    }

    @Override
    public void onFailure(@NotNull Exception e) {

    }
});