SDK

Remote Notifications

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

If you are using the default re.notifica.app.DefaultIntentReceiver you will not have any control in how messages are handled. If you've followed our recommendation and have implemented your own Intent Receiver, you will have a way of intercepting and customize the way messages are displayed in your app.

Notificare library accepts 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.

Handling opens from Notification Manager

Since Android 12, apps are forced to handle notification opens in an activity. The Notificare Android SDK will launch an intent re.notifica.intent.action.RemoteMessageOpened whenever one is clicked. As described in the Implementation guide, your (main) activity will have to handle this intent:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        handleIntent(getIntent());
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.i(TAG, "new intent: " + intent.getData());
        handleIntent(intent);
    }
    
    protected void handleIntent(Intent intent) {
        Uri data = intent.getData();
        if (Notificare.shared().handleTrampolineIntent(intent)) {
            Log.d(TAG, "trampoline intent handled");
        } else {
            // not a RemoteMessageOpened intent, handle the intent as usual
        }
    }    

}
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        handleIntent(intent)
    }
    
    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        handleIntent(intent)
    }
    
    fun handleIntent(intent: Intent?) {
        if (Notificare.shared().handleTrampolineIntent(intent)) {
            Log.d(TAG, "trampoline intent handled");
        } else {
            // not a RemoteMessageOpened intent, handle the intent as usual
        }
    }
}

Also don't forget to add the necessary intent filter to your AndroidManifest.xml :

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

Receiving Notifications

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. Simply add the following line in your Application class:

public class MyBaseApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        //...more code

        // Launch Notificare system
        Notificare.shared().launch(this);

        // After launch you can create a channel
        // For Android Oreo add this line
        Notificare.shared().createDefaultChannel();

        //...more code
    }

    //...more code

}
class MyBaseApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        //... more code

        // Launch Notificare system
        Notificare.shared().launch(this)

        // After launch you can create a channel
        // For Android Oreo add this line
        Notificare.shared().createDefaultChannel()

        //...more code
    }

    //...more code
}

That is enough to make notifications work in Android 8. 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, invoke the following method:

Notificare.shared().setDefaultChannel(myChannel.getId());
Notificare.shared().defaultChannel = myChannel.id

If you are using our Loyalty add-on and you want all the relevance and update notifications to be in a separated channel, you can set as follows:

Notificare.shared().setPassbookChannel(myPassbookChannel.getId());
Notificare.shared().passbookChannel = myPassbookChannel.id

Background Notifications

Once you're receiving notifications in your app, we can dive deeper and fully understand how they are handled. In most cases, developers will not need to take any action in order to display notifications. If you want to handle incoming notifications in your own Intent Receiver, for example to add a badge to your application launcher icon, you will most likely implement a method like this one:

@Override
public void onNotificationReceived(NotificareRemoteMessage message) {

    // Call default handling of incoming notification
    super.onNotificationReceived(message);

    // Handle UI updates in your own app, like presenting a badge
    MyBadgeUtilsInstance.showBadge(Notificare.shared().getInboxManager().getUnreadCount());
}
override fun onNotificationReceived(message: NotificareRemoteMessage?) {
    // Call default handling of incoming notification
    super.onNotificationReceived(message)

    // Handle UI updates in your own app, like presenting a badge
    MyBadgeUtilsInstance.showBadge(Notificare.shared().inboxManager.unreadCount);
}

The DefaultIntentReceiver method handles everything, from adding notifications to the notification drawer to logging events, so it is important to call super.onNotificationReceived(message) and only use your override to do extra work and not interfere with the normal flow of handling notifications.

Another example of cases where you might want to intercept interactions in a notification is when users engage with actionable notifications. Extending the content of a notification with actionable buttons that can quickly collect the input a user are one of the things marketers love about Notificare, because of that you might need at some point to acknowledge when those actions are clicked. When you implement your own Intent Receiver these actions can be easily be intercepted by simply overriding the following method:

public class MyIntentReceiver extends DefaultIntentReceiver {

    //...more code

    @Override
    public void onActionReceived(Uri target) {
        Log.d(TAG, "Custom action was received: " + target.toString());
        super.onActionReceived(target);
    }

    //...more code

}
class MyIntentReceiver: DefaultIntentReceiver() {
    override fun onActionReceived(target: Uri?) {
        Log.d(TAG, "Custom action was received: " + target.toString())
        super.onActionReceived(target)
    }
}

Foreground Notifications

As explained above, incoming notifications are handled by your intent receiver, whether your app is running in foreground or in background. If you also would like to have your Activity be notified when a notification comes in, your Activity should implement the OnNotificareNotificationListener interface In the example below, a Snackbar is displayed when a notification comes in.

@Override
protected void onResume() {
    super.onResume();
    Notificare.shared().addNotificareNotificationListener(this);
}

@Override
protected void onPause() {
    super.onPause();
    Notificare.shared().removeNotificareNotificationListener(this);
}

@Override
public void onNotificareNotification(NotificareNotification notificareNotification, NotificareInboxItem notificareInboxItem, Boolean shouldPresent) {
    Log.i(TAG, "foreground notification received");
    if (shouldPresent) {
        Log.i(TAG, "should present incoming notification");
        Snackbar notificationSnackbar = Snackbar.make(findViewById(R.id.content_frame), notificareNotification.getMessage(), Snackbar.LENGTH_INDEFINITE);
        notificationSnackbar.setAction(R.string.open, view -> {
            if (notificareInboxItem != null) {
                Notificare.shared().openInboxItem(this, notificareInboxItem);
            } else {
                Notificare.shared().openNotification(this, notificareNotification);
            }
        });
        notificationSnackbar.show();
    }
}
override fun onResume() {
    super.onResume()
    Notificare.shared().addNotificareNotificationListener(this)
}

override fun onPause() {
    super.onPause()
    Notificare.shared().removeNotificareNotificationListener(this)
}

fun onNotificareNotification(notificareNotification: NotificareNotification, notificareInboxItem: NotificareInboxItem?, shouldPresent: Boolean) {
    Log.i(TAG, "foreground notification received")
    if (shouldPresent) {
        Log.i(TAG, "should present incoming notification")
        val notificationSnackbar = Snackbar.make(findViewById(R.id.content_frame), notificareNotification.message, Snackbar.LENGTH_INDEFINITE)
        notificationSnackbar.setAction(R.string.open) { view: View? ->
            if (notificareInboxItem != null) {
                Notificare.shared().openInboxItem(this, notificareInboxItem)
            } else {
                Notificare.shared().openNotification(this, notificareNotification)
            }
        }
        notificationSnackbar.show()
    }
}

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 an 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:

<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.mydomain" />
    </intent-filter>
</activity>

By simply providing a data entry in your activity Intent Filter element, any click in a link from a web page using com.mydomain://mydomain.com/product?id=1 as the href value, would simply trigger your app to open. 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:

public class DeeplinkActivity extends ActionBarBaseActivity{

    ...more code

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_deeplink);

        //...more code

        Uri data = getIntent().getData();
        if (data != null) {
            String path = data.getPath();
            //Open a view based on the path
        }

    }

    //...more code

}
class DeeplinkActivity : ActionBarBaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_deeplink)

        //...more code

        val data = intent.data
        if (data != null) {
            val path = data.path
            //Open a view based on the path
        }
    }

    //...more code

}

Finally, when using our SDK 2.4 and up, we've added support for Universal Links. 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="http"/>
        <data android:host="demo.ntc.re" android:scheme="https"/>
    </intent-filter>
</activity>

And handle those intents:

public class DeeplinkActivity extends ActionBarBaseActivity{

    ...more code

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_deeplink);

        //...more code

        if (Notificare.shared().handleDynamicLinkIntent(this, intent) {

            // this was a Notificare Dynamic link, 
            // your app will get another intent with the Android target URL
            // that was set in the dynamic link

        } else { 

            // your other intent handling routines

        }

    }

    //...more code

}
class DeeplinkActivity : ActionBarBaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_deeplink)

        //...more code

        if (Notificare.shared().handleDynamicLinkIntent(this, intent) {
        
            // this was a Notificare Dynamic link, 
            // your app will get another intent with the Android target URL
            // that was set in the dynamic link
    
        } else { 
    
            // your other intent handling routines
    
        }
    }

    //...more code

}
If you need to open external deeplinks 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.metadata.UrlSchemes" android:resource="@array/url_schemes" />

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

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="url_schemes">
        <item>com.mydomain</item>
        <item>com.mydomain2</item>
        <item>com.mydomain3</item>
    </string-array>
</resources>

Any click in a HTML or Web Page notification type, would be intercepted by our library and trigger the following method in your Intent Receiver:

public class MyIntentReceiver extends DefaultIntentReceiver {

    //...more code

    @Override
    public void onUrlClicked(Uri urlClicked, Bundle extras) {
        Log.i(TAG, "URL was clicked: " + urlClicked);
        NotificareNotification notification = extras.getParcelable(Notificare.INTENT_EXTRA_NOTIFICATION);
        if (notification != null) {
            Log.i(TAG, "URL was clicked for \"" + notification.getMessage() + "\"");
        }
    }

    //... more code
}
class MyIntentReceiver: DefaultIntentReceiver() {

    //... more code

    override fun onUrlClicked(urlClicked: Uri, extras: Bundle) {
        Log.i(TAG, "URL was clicked: $urlClicked")
        val notification: NotificareNotification? = extras.getParcelable(Notificare.INTENT_EXTRA_NOTIFICATION)
        if (notification != null) {
            Log.i(TAG, "URL was clicked for \"" + notification.message + "\"")
        }
    }

    //... more code
}

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 Inbox Manager. 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:

if (Notificare.shared().getInboxManager() != null) {
    LiveData<SortedSet<NotificareInboxItem>> temp = Notificare.shared().getInboxManager().getObservableItems();
    temp.observe(this, notificareInboxItems -> {
        Log.i(TAG, "inbox changed");
        inboxListAdapter.clear();
        if (notificareInboxItems != null) {
            inboxListAdapter.addAll(notificareInboxItems);
        }
    });
}
if (Notificare.shared().inboxManager != null) {
    val temp = Notificare.shared().inboxManager.observableItems
    temp.observe(this, Observer { 
        notificareInboxItems: SortedSet<NotificareInboxItem?>? ->
            Log.i(AssetLoader.TAG, "inbox changed")
            inboxListAdapter.clear()
            if (notificareInboxItems != null) {
                inboxListAdapter.addAll(notificareInboxItems)
            }
        }
    )
}

If you don't want to use LiveData, you still can get all the items in the inbox:

if (Notificare.shared().getInboxManager() != null) {
    for (NotificareInboxItem item : Notificare.shared().getInboxManager().getItems()) {
        inboxListAdapter.add(item);
    }
}
if (Notificare.shared().inboxManager != null) {
    for (item in Notificare.shared().inboxManager.items) {
        inboxListAdapter.add(item)
    }
}

In the example above we assume that you will create a list adapter that will be populated by the Inbox Manager. In a real app, you probably would use something a bit more sophisticated, like a RecyclerView that only updates the changes in the list.

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

LiveData<Integer> unreadCount = Notificare.shared().getInboxManager().getObservableUnreadCount();
unreadCount.observe(this, count -> {
    setMyBadge(count);
});
val unreadCount = Notificare.shared().inboxManager.observableUnreadCount
unreadCount.observe(this, Observer { 
    count: Int? -> setMyBadge(count) 
})

Without using LiveData, to retrieve the number of unread messages you can simply call the following method:

int unread = Notificare.shared().getInboxManager().getUnreadCount();
val unread = Notificare.shared().inboxManager.unreadCount

Assuming that your list adapter is assigned to a list view, to open an inbox item you would simply do something like this:

listView.setOnItemClickListener((parent, view, position, id) -> {
    NotificareInboxItem item = inboxListAdapter.getItem(position);
    Notificare.shared().openInboxItem(getActivity(), item);
});
listView.onItemClickListener = OnItemClickListener { parent: AdapterView<*>?, view: View?, position: Int, id: Long ->
        val item: NotificareInboxItem = inboxListAdapter.getItem(position)
        Notificare.shared().openInboxItem(activity, item)
    }

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

//...more code

NotificareInboxItem item = inboxListAdapter.getItem(position);

// Removes item
Notificare.shared().getInboxManager().removeItem(item, new NotificareCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean aBoolean) {
        // Inbox item removed
    }

    @Override
    public void onError(NotificareError notificareError) {
        // Failed to remove inbox item
    }
});

//...more code
//...more code

val item: NotificareInboxItem = inboxListAdapter.getItem(position)

// Removes item
Notificare.shared().inboxManager.removeItem(item, object : NotificareCallback<Boolean?> {
    override fun onSuccess(aBoolean: Boolean?) {
        // Inbox item removed
    }

    override fun onError(notificareError: NotificareError) {
        // Failed to remove inbox item
    }
})

//...more code

Since the list adapter in your app is an observer to the inbox, it will be updated automatically

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

//...more code

NotificareInboxItem item = inboxListAdapter.getItem(position);

// Marks item as read
Notificare.shared().getInboxManager().markItem(item, new NotificareCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean aBoolean) {
        // Inbox item marked as read
    }

    @Override
    public void onError(NotificareError notificareError) {
        // Failed to mark inbox item
    }
});

//...more code
//...more code

val item: NotificareInboxItem = inboxListAdapter.getItem(position)

// Marks item as read
Notificare.shared().inboxManager.markItem(item, object : NotificareCallback<Boolean?> {
    override fun onSuccess(aBoolean: Boolean?) {
        // Inbox item marked as read
    }

    override fun onError(notificareError: NotificareError) {
        // Failed to mark inbox item
    }
})

//...more code

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

//...more code

// Mark all items as read
Notificare.shared().getInboxManager().markAll(new NotificareCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean aBoolean) {
        // Inbox items marked as read
    }

    @Override
    public void onError(NotificareError notificareError) {
        // Failed to mark inbox items
    }
});

//...more code
//...more code

// Mark all items as read
Notificare.shared().inboxManager.markAll(object : NotificareCallback<Boolean?> {
    override fun onSuccess(aBoolean: Boolean?) {
        // Inbox items marked as read
    }

    override fun onError(notificareError: NotificareError) {
        // Failed to mark inbox items
    }
})

//...more code

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

//...more code

// Clears inbox
Notificare.shared().getInboxManager().clearInbox(new NotificareCallback<Integer>() {
    @Override
    public void onSuccess(Integer size) {
        // Inbox is cleared
    }

    @Override
    public void onError(NotificareError notificareError) {
        // Failed to clear inbox
    }
});
//...more code

// Clears inbox
Notificare.shared().inboxManager.clearInbox(object : NotificareCallback<Int?> {
    override fun onSuccess(size: Int?) {
        // Inbox is cleared
    }

    override fun onError(notificareError: NotificareError) {
        // Failed to clear inbox
    }
})

Again, since the list adapter in your app is an observer to the inbox, it will be updated automatically.