Android Setup
Inbox
The Inbox (sometimes referred to as Notification Center) is a Rover feature for presenting a user's previously received notifications to them.
There are two means for using the Inbox in your app: using Rover's provided UI (either an Activity for presenting modally or a custom view for embedding within your own UI) with your own customizations, or consuming the Notification Store API for building your own bespoke UI.
Presenting Modally
The Notifications module contains a full Activity, InboxActivity
, with everything needed to fetch and display the user's notifications in a familiar list view. Additionally it supports functionality for marking notifications as read and allowing the user to delete notifications when they are no longer needed.
You can launch it like this:
startActivity(
InboxActivity.makeIntent(this)
)
However, in its bare form it is likely not appropriate for your product. If you add an entry for InboxActivity
to your manifest, you can change the theme.
Presenting within an Existing Activity/Layout
The Notification Center is also available as an Android view, InboxListView
, that can be embedded anywhere within your application.
You may embed it in your Fragment, Activity, or even another custom view with either layout XML or directly in code.
Embed it in an XML view with:
<io.rover.sdk.notifications.ui.InboxListView
android:id="@+id/Inbox"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
AppCompat Theme Needed
The InboxListView requires an AppCompat-based theme, otherwise it may look incorrect or even crash. We recommend using Theme.AppCompat.Light
. Either set the containing activity's theme to an AppCompat based theme, or when doing programmatic embeds (including when using Jetpack Compose's AndroidView
) use ContextThemeWrapper
to inject the needed theme.
Then, from the code side of your Activity or Fragment, in either onCreate()
or
Then, from the code side of your Activity or Fragment, in either onCreate()
or onCreateView()
respectively, you will need to provide a reference to the Activity to the view:
(findViewById(R.id.demoNotificationCentre) as InboxListView).activity = this
Then, in order to open your Notification Center whenever a Campaign is configured to open the app to the notification center, tell the Rover Notifications module where to find your embedded Notification Center by providing the NotificationsAssembler with an Intent for it:
Rover.init(
/* ... */
NotificationsAssembler(
/* ... */
InboxIntent = /* AN_INTENT_FOR_OPENING_YOUR_APP_TO_YOUR_NOTIFICATION_SCREEN */
)
)
Customizing Rover's Inbox UI
The Notification Center can (and likely should) be customized to suit your product design. The mechanism for this is to override the factories for views used for the Notification Center within the Rover SDK dependency injection container.
Please see the Customizing Services guide for further direction on overriding factories.
Customize Notification Row Views
You can either create a whole new custom view, or override the bundled NotificationItemView
. If you just want to use your own layout, XML or otherwise, then the latter may be a good option.
To use your own XML layout create a subclass of NotificationItemView
and override the buildLayout()
, bind()
, and bindThumbnail()
methods.
import io.rover.sdk.notifications.ui.NotificationItemView
import android.view.LayoutInflater
class MyCustomNotificationItemView: NotificationItemView {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun buildLayout() {
// inflate and attach your XML view layout, or build your layout manually with code.
LayoutInflater.from(context).inflate(R.layout.my_notification_center_item, this)
}
override fun bind(viewModel: NotificationItemViewModelInterface) {
findViewById<TextView>(R.id.my_body_text_view).text = viewModel.notificationForDisplay.body
/* ... */
}
override fun bindThumbnail(bitmap: Bitmap) {
val imageView = findViewById<AppCompatImageView>(R.id.my_image_view)
imageView.scaleType = ImageView.ScaleType.CENTER_CROP // Fill.
imageView.setImageBitmap(bitmap)
}
}
Disable Thumbnail Image Views
If your design calls for the rows to have no thumbnail image views, then be sure to override bindThumbnail()
with an empty implementation.
Default implementation
You can refer to the original version of built-in NotificationItemView.
Then override the factory for notification rows to return instances of your subclass instead of the built-in one:
Rover.init(
/* ... */
NotificationsAssembler(/* ... */),
// we create a little anonymous assembler here to override the previous ones.
// Ensure that you add it in the list after NotificationsAssembler!
object : Assembler {
override fun assemble(container: Container) {
container.register(
Scope.Transient,
BindableView::class.java,
"notificationItemView"
) { resolver, context: Context ->
MyCustomNotificationItemView(context)
}
}
}
)
Customize Empty State Screen
Override the factory for notification center list empty view to return whatever view you would like to be displayed whenever the list is empty:
Rover.init(
/* ... */
NotificationsAssembler(/* ... */),
// we create a little anonymous assembler here to override the previous ones.
// Ensure that you add it in the list after NotificationsAssembler!
object : Assembler {
override fun assemble(container: Container) {
container.register(
Scope.Transient,
BindableView::class.java,
"notificationListEmptyArea"
) { resolver, context: Context ->
// Just using a text view, as an example:
TextView(context).apply {
text = "No notifications yet!"
}.apply {
// center it in the display.
gravity = Gravity.CENTER_VERTICAL or Gravity.CENTER_HORIZONTAL
}
}
}
}
)
Customize Swipe to Delete
The notification center features the fairly common swipe-to-delete pattern. If the item is swiped all the way to the left, it is removed from the notification center.
Override the factory for notification center list empty view to return whatever view you would like to be displayed whenever the list is empty. By default this is a red shaded area with a trashcan icon.
Rover.init(
/* ... */
NotificationsAssembler(/* ... */),
// we create a little anonymous assembler here to override the previous ones.
// Ensure that you add it in the list after NotificationsAssembler!
object : Assembler {
override fun assemble(container: Container) {
container.register(
Scope.Transient,
BindableView::class.java,
"notificationItemSwipeToDeleteBackgroundView"
) { resolver, context: Context ->
LayoutInflater.from(context).inflate(
R.layout.my_notification_center_default_item_delete_swipe_reveal,
)
}
}
}
)
Swipe to Reveal Area Layout XML
You can refer to the original version of our swipe to reveal area in XML at notification_center_default_item_delete_swipe_reveal.xml.
Using your own UI
If you want to instead use your own UI (perhaps with notifications from other sources aggregated into the same view), you can instead use the SDK's NotificationStore
to retrieve the notifications and display them in your own UI.
Retrieving Notifications
The Rover NotificationStore gives you access to the list of all notifications received by this device. You can subscribe to this list of notifications and display them in your own UI. The API is reactive-style, so you can subscribe to changes to the list of notifications and update your UI accordingly.
The NotificationStore
has a notifications
property that is a Reactive Streams Publisher of a list of notification objects. You subscribe to this property to be notified whenever the list of notifications changes, allowing you to have a UI that live updates as notifications are received or interacted with. A recommended way of consuming this publisher is to convert it to a Kotlin Flow.
Note that you will likely need to filter the notifications to only show non-deleted notifications and ones that have the "Notification Center" (another term for the Inbox) delivery method enabled.
val notificationsPublisher = Rover.shared.notificationStore.notifications()
val notificationsFlow = notificationsPublisher.asFlow()
Here is a Jetpack Compose example for consuming the flow of notifications in a UI:
// Get the Notifications publisher and convert it to a Kotlin flow:
val notificationFlow = remember {
Rover.shared.notificationStore.notifications().asFlow()
}
// Convert the flow into a StateFlow for Jetpack Compose to subcribe to:
val notifications by notificationFlow.collectAsState(
initial = emptyList()
)
// Compute the list of visible notifications:
val visibleNotifications = remember(notifications) {
notifications.filter { it.isNotificationCenterEnabled && !it.isDeleted }
}
// Display the notification:
visibleNotifications.forEach { notification ->
Text(notification.title ?: "Untitled")
}
Opening Notifications
To open a notification, the following steps will need to be performed:
- Marking the notification as read
- Tracking conversions
- Adding the notification opened event to the event queue
- Handling the notification's tap behavior
The following sample code demonstrates how to perform these steps for your custom UI.
fun openNotification(notification: Notification) {
// 1. Mark the notification as read
Rover.shared.notificationStore.markRead(notification)
// 2. Perform conversion tracking
notification.conversionTags?.let { conversionTags ->
val conversionTrackerService = Rover.shared.resolve(ConversionsTrackerService::class.java)
conversionTrackerService?.trackConversions(conversionTags)
}
// 3. Add the event to the event queue
Rover.shared.eventQueue.trackEvent(
Event(
"Notification Opened",
hashMapOf(
Pair("notification", notification.asAttributeValue()),
Pair("source", "notificationCenter")
)
),
"rover"
)
// 4. Handle the notification's tap behavior
val intent = when (notification.tapBehavior) {
// Not appropriate to re-open the app when opening notification directly, do nothing.
is Notification.TapBehavior.OpenApp -> null
// Open the URI as required by your app.
is Notification.TapBehavior.OpenUri -> // return an intent to perform custom behavior for opening the URI
// Present the website as appropriate for your app.
is Notification.TapBehavior.PresentWebsite -> // return an intent to open the website in a web browser.
}
if (intent != null) {
try { context.startActivity(intent) } catch (e: ActivityNotFoundException) { null }
}
}
Marking Notifications as Deleted
Call the delete()
store method to mark a notification as deleted. The publisher will update reflecting the change.
fun deleteNotification(notification: Notification) {
Rover.shared.notificationStore.delete(notification)
}
Delivering Notifications into the Inbox
To test your notification center implementation, you can use Rover to
When authoring a campaign in the Rover Campaigns app, you have the option to enable "Notification Center" as one of the ways to deliver the campaign.
When Notification Center is enabled, the notification delivered by the campaign will be accessible in your app's Notification Center in addition to the system-displayed push notification.
Then you can use the "Send a Test" button to send a test notification to your device.