Skip to content

Customize Android notifications#

Android needs a persistent notification to keep the app alive during the navigation tracking.

By default a simple notification is shown, which has some basic configuration options.

Configure the basic notification#

Is possible to customize the icon, and the notification title, using the meta-data tag in the AndroidManifest.xml The available keys are:

  • com.citymapper.sdk.notification_title for the title of the notification
  • com.citymapper.sdk.notification_icon for the icon of the notification
<meta-data
    android:name="com.citymapper.sdk.notification_title"
    android:value="@string/notification_title" />

<meta-data
    android:name="com.citymapper.sdk.notification_icon"
    android:resource="@drawable/ic_stat" />

From android oreo (API 26), notifications need a channel, you can change the channel name using the key com.citymapper.sdk.notification_channel

<meta-data
    android:name="com.citymapper.sdk.notification_channel"
    android:value="@string/notification_channel" />

Show a completely custom notification#

For more control over the notification, implement a custom foreground Service which will run while navigation is active.

Specify the custom service class in the TrackingConfiguration object when starting navigation, or retrieving a NavigableRoute.

val trackingConfiguration = TrackingConfiguration(
  foregroundServiceClass = CustomForegroundService::class.java
)

It is strongly recommended that this service extends from BaseNavigationForegroundService which gives a simple API for building a notification from the latest RouteProgress information, and automatically handles the service lifecycle.

class CustomForegroundService : BaseNavigationForegroundService<CustomForegroundService.State>() {

  private val channel by lazy {
    val channel = NotificationChannelCompat.Builder(
      "notification-channel-custom",
      NotificationManager.IMPORTANCE_DEFAULT
    )
      .setName("My custom navigation notification")
      .setSound(null, null)
      .setVibrationEnabled(false)
      .setShowBadge(false)
      .build()

    NotificationManagerCompat.from(this).createNotificationChannel(channel)

    channel
  }

  private val contentIntent by lazy {
    PendingIntent.getActivity(
      this,
      0,
      Intent(this, MyNavigationActivity::class.java).apply {
        addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
      },
      PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
    )
  }

  override fun createNotificationModel(routeProgress: RouteProgress): State {
    val secondsRemaining = routeProgress.durationSecondsRemaining
      ?: return State(title = "Loading...")

    return State(title = "${secondsRemaining / 60} minutes left!")
  }

  override fun getNotification(notificationModel: State?): Notification {
    return NotificationCompat.Builder(this, channel.id)
      .setContentTitle(notificationModel?.title ?: "Loading...")
      .setSmallIcon(R.drawable.my_notification_icon)
      .setContentIntent(contentIntent)
      .build()
  }

  // To prevent unneccesary notification updates, this must be a data class
  // or correctly implement `equals`
  data class State(val title: String)
}