Skip to content

Android Custom Notification Layout

Chris Scott edited this page Sep 24, 2026 · 2 revisions

Android only. The SDK tracks location in a foreground service, and Android requires a foreground service to show a persistent notification. You can style that notification with app.notification.title, text, smallIcon, largeIcon and color. If that is not enough, for example if you want your own buttons, supply your own layout:

A custom layout uses three NotificationConfig options:

Option What it does
layout The name of your layout file in res/layout, without .xml
strings Text for your own <TextView /> elements, keyed by android:id
actions The android:ids of your <Button /> elements; clicks arrive in onNotificationAction

Step 1: Create a layout file

In Android Studio, select File → New → XML → Layout XML File:

Enter a Layout File Name (for example notification_layout) and leave Root Tag as LinearLayout. The file is created at app/src/main/res/layout/notification_layout.xml:

⚠️ A notification layout is inflated as RemoteViews, so it can contain only the views that RemoteViews supports. Use elements such as LinearLayout, FrameLayout, RelativeLayout, TextView, ImageView and Button. ConstraintLayout and your own custom views do not work in a notification.

Step 2: Edit your layout

You will mostly be adding <TextView />, <ImageView /> and <Button /> elements. The SDK finds each element by its android:id.

Elements the SDK fills in

When the SDK renders your layout, it looks for these ids and fills each one it finds:

android:id Filled with
applicationName Your app's name (the android:label in AndroidManifest.xml)
notificationTitle app.notification.title (your app's name if title is empty)
notificationText app.notification.text
notificationSmallIcon app.notification.smallIcon (defaults to your app icon)
notificationLargeIcon app.notification.largeIcon (left alone when largeIcon is not set)

Every id is optional: leave out any element you don't want.

Your own <TextView /> elements

Add any <TextView /> with an id of your choice:

<TextView
    android:id="@+id/myCustomElement"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="myCustomElement" />

Then fill it with app.notification.strings, a map from android:id to text. See Step 3.

Your own <Button /> elements

Add a <Button /> for each action:

<Button
    android:id="@+id/notificationButtonFoo"
    style="@style/Widget.AppCompat.Button.Small"
    android:layout_width="60dp"
    android:layout_height="40dp"
    android:text="Foo" />

Then list each button's id in app.notification.actions and listen for clicks with onNotificationAction. See Step 3.

The ids of your own elements can be any name. The SDK looks each one up by the name you give in strings or actions. The examples on this page prefix them with notification only to keep them apart from your app's other ids.

Sample layout

As a starting point, copy this into your new file:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="135dp"
    android:gravity="start"
    android:orientation="vertical"
    android:padding="15dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="15dp"
        android:gravity="center"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/notificationSmallIcon"
            android:layout_width="16dp"
            android:layout_height="16dp"
            android:tint="@android:color/background_dark"
            tools:srcCompat="@tools:sample/avatars" />

        <TextView
            android:id="@+id/applicationName"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:paddingLeft="10dp"
            android:text="applicationName"
            android:textAppearance="@style/TextAppearance.Compat.Notification.Title"
            android:textColor="#888888"
            android:textSize="12sp" />
    </LinearLayout>

    <TextView
        android:id="@+id/notificationTitle"
        style="@style/TextAppearance.Compat.Notification.Title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="notificationTitle"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/notificationText"
        style="@style/TextAppearance.Compat.Notification.Line2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="notificationText"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/myCustomElement"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="myCustomElement" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="end"
        android:orientation="horizontal">

        <Button
            android:id="@+id/notificationButtonFoo"
            style="@style/Widget.AppCompat.Button.Small"
            android:layout_width="60dp"
            android:layout_height="40dp"
            android:text="Foo" />

        <Button
            android:id="@+id/notificationButtonBar"
            style="@style/Widget.AppCompat.Button.Small"
            android:layout_width="60dp"
            android:layout_height="40dp"
            android:text="Bar" />
    </LinearLayout>
</LinearLayout>

The android:text values are placeholders for the layout preview. The SDK replaces them when it renders the notification.

Android limits the height of a custom notification. See Create a custom notification layout. Check your layout on a device in both its collapsed and expanded states.

Step 3: Use your layout

Kotlin

val bgGeo = BGGeo.instance

// Listen for clicks on the buttons listed in app.notification.actions.
bgGeo.onNotificationAction { buttonId ->
    when (buttonId) {
        "notificationButtonFoo" -> { /* Handle [Foo] */ }
        "notificationButtonBar" -> { /* Handle [Bar] */ }
    }
}.storeIn(subscriptions)

// Within a coroutine scope, e.g. lifecycleScope.launch { }
bgGeo.ready {
    app.notification.layout = "notification_layout"  // res/layout/notification_layout.xml
    app.notification.title = "The title"
    app.notification.text = "The text"
    app.notification.strings = mapOf(
        "myCustomElement" to "My custom element text"
    )
    app.notification.actions = listOf(
        "notificationButtonFoo",
        "notificationButtonBar"
    )
}

(subscriptions is a MutableSet<EventSubscription> you close in onDestroy(), as in the Setup guide.)

To change the text while tracking, edit the configuration. The SDK re-renders the notification:

bgGeo.config.edit {
    app.notification.strings = mapOf("myCustomElement" to "Updated text")
}

strings replaces the whole map, so include every custom element each time.

Java

TSConfig config = TSConfig.getInstance(getApplicationContext());
Editor editor = config.edit();

Map<String, String> strings = new HashMap<>();
strings.put("myCustomElement", "My custom element text");

editor.app().notification()
        .setLayout("notification_layout")
        .setTitle("The title")
        .setText("The text")
        .setStrings(strings)
        .setActions("notificationButtonFoo", "notificationButtonBar");
editor.commit();

// `this` is your Activity.
BackgroundGeolocation.getInstance(this).onNotificationAction(new TSNotificationActionCallback() {
    @Override
    public void onClick(String buttonId) {
        switch (buttonId) {
            case "notificationButtonFoo":
                // Handle [Foo]
                break;
            case "notificationButtonBar":
                // Handle [Bar]
                break;
        }
    }
});

(TSConfig is com.transistorsoft.locationmanager.config.TSConfig, Editor is com.transistorsoft.locationmanager.config.edit.Editor, BackgroundGeolocation is com.transistorsoft.locationmanager.adapter.BackgroundGeolocation and TSNotificationActionCallback is com.transistorsoft.locationmanager.adapter.callback.TSNotificationActionCallback.) Use setStrings(map) rather than setString(key, value). In SDK 4.6.0 and earlier, setString replaces the whole map with that one entry.

Button clicks while your app is terminated

With app.stopOnTerminate = false, the SDK keeps tracking after your app is terminated, and the buttons on its notification keep working. The SDK does not call your onNotificationAction listener in that state. Instead, with app.enableHeadless = true, each click goes to your headless task as a NOTIFICATIONACTION event, whose payload is the button id. With enableHeadless off, the click is dropped. See Android Headless Mode.

Troubleshooting

Set logger.logLevel to warning or higher, then watch the SDK's log with adb logcat -s TSLocationManager. These lines are warnings and errors, so at the default level, off, the SDK writes none of them:

Log line Meaning
Could not find custom notification layout '<name>' in app/src/main/res/layout No layout named <name>. Use the file name without .xml. The SDK falls back to its standard notification.
Failed to find TextView resource: <id> A key in app.notification.strings matches no android:id in your app.
Failed to find Button resource in notification_layout for notification-action: <id> An entry in app.notification.actions matches no android:id in your app.
Failed to find ImageView id: notificationLargeIcon in notification layout You set app.notification.largeIcon, but your layout has no notificationLargeIcon element.
Failed to decode app.notification.largeIcon (vector/xml requires Drawable): <name> No drawable or mipmap resource matches app.notification.largeIcon, or it couldn't be loaded.
Failed to resolve app.notification.smallIcon: <name> No drawable or mipmap resource matches app.notification.smallIcon. The SDK uses your app icon instead.

The SDK finds your layout and its elements by name at runtime. If you enable resource shrinking (isShrinkResources = true), tell the shrinker to keep the layout with a keep file of your own, for example res/raw/notification_layout_keep.xml. Don't name it keep.xml: the SDK ships its own res/raw/keep.xml, and a file with that name in your app replaces it, so the shrinker can then remove the SDK's own resources:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/notification_layout" />

See Customize which resources to keep.