Skip to content

Android Custom Notification Layout

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

On Android, the SDK runs a foreground service while it tracks location, and Android requires every foreground service to show a notification. If the default notification doesn't suit your needs (for example, you want to add your own buttons), you can design your own notification layout:

A custom foreground-service notification with buttons

Step 1 — Create a custom layout file:

Open your project's android folder in Android Studio. Select File->New->XML->Layout XML File:

Android Studio: File > New > XML > Layout XML File

Enter a layout name. Android resource names may contain only lowercase letters, digits and underscores (for example, my_notification_layout). Android Studio creates your file in the folder android/app/src/main/res/layout/:

Android Studio: entering the layout name

Step 2 — Edit your layout:

Even if you have no experience with Android layouts, the basics are easy to pick up. You'll mostly be adding <TextView />, <ImageView /> and <Button /> elements. Android renders notification layouts with RemoteViews, which supports only a fixed set of views. Stick to layouts such as <LinearLayout />, <FrameLayout /> and <RelativeLayout />, and elements such as <TextView />, <ImageView /> and <Button />. Android can't display a notification layout that contains any other kind of view.

The key thing to understand is the android:id of these elements. The SDK finds each element by its android:id and fills it from your app.notification configuration.

Layout Special Elements

When BackgroundGeolocation renders your custom notification layout, it looks for the following android:ids. When it finds one, it fills that element from the corresponding data source:

Layout element android:id Data source
applicationName Your application name (android:label in AndroidManifest.xml)
notificationTitle app.notification.title (defaults to "Background Geolocation"; set title: '' to show your application name instead)
notificationText app.notification.text
notificationSmallIcon app.notification.smallIcon (defaults to your app icon)
notificationLargeIcon app.notification.largeIcon (filled only when you set largeIcon)

You choose the android:id of every other element. The SDK finds your own elements by the ids you list in app.notification.strings and app.notification.actions (see below).

Set app.notification.layout to the name of your layout file, without the .xml extension:

BackgroundGeolocation.ready({
  app: {
    notification: {
      layout: 'my_notification_layout',  // <-- res/layout/my_notification_layout.xml
      title: 'The Notification Title',
      text: 'The Notification Text',
      smallIcon: 'mipmap/my_small_icon', // <-- defaults to app icon
      largeIcon: 'mipmap/my_large_icon'
    }
  }
});

Custom <TextView /> Elements

You can declare your own custom <TextView /> elements and render text into them with the app.notification.strings parameter.

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

Each key in app.notification.strings is the android:id of a <TextView />, and its value is the text to render:

BackgroundGeolocation.ready({
  app: {
    notification: {
      strings: {
        myCustomElement: 'My Custom Element Text'  // <-- android:id="@+id/myCustomElement"
      }
    }
  }
});

Custom <Button /> Elements:

You can declare your own custom <Button /> elements and register click-listeners on them with the app.notification.actions parameter:

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

List the android:id of each button in app.notification.actions, then listen for clicks with BackgroundGeolocation.onNotificationAction:

BackgroundGeolocation.ready({
  app: {
    notification: {
      actions: [  // <-- register button listeners
        'notificationButtonFoo',
        'notificationButtonBar'
      ]
    }
  }
});

// Listen to custom button clicks:
BackgroundGeolocation.onNotificationAction((buttonId) => {
  console.log('[onNotificationAction] - ', buttonId);
  switch (buttonId) {
    case 'notificationButtonFoo':
      break;
    case 'notificationButtonBar':
      break;
  }
});

⚠️ Known issue in React Native SDK 5.x: with @transistorsoft/background-geolocation-types 5.3.3 or earlier installed, onNotificationAction throws BackgroundGeolocation#on must be provided a {String} event as 1st argument. and registers no listener. The fix is in that types package, version 5.3.4. Every 5.x release of the SDK accepts it, so updating it fixes any 5.x install without upgrading the SDK:

npm update @transistorsoft/background-geolocation-types

npm ls @transistorsoft/background-geolocation-types shows the version you have.

While your app is terminated, onNotificationAction can't run. With app.stopOnTerminate: false and app.enableHeadless: true, button clicks go to your headless task instead, as an event named notificationaction whose params is the button's android:id:

📂 index.js

import BackgroundGeolocation from 'react-native-background-geolocation';

const BGHeadlessTask = async (event) => {
  switch (event.name) {
    case 'notificationaction':
      console.log('[BGHeadlessTask] notification button clicked:', event.params);
      break;
  }
};

BackgroundGeolocation.registerHeadlessTask(BGHeadlessTask);

Note

In TypeScript, event.params is declared as Record<string, any>, but for notificationaction it is the button's android:id string. Cast it: const buttonId = event.params as unknown as string;. With @transistorsoft/background-geolocation-types 5.3.3 or earlier, case 'notificationaction': also fails to type-check. Update the types package as described in the known issue above.

Sample Layout

As a starting point for your custom layout, copy the following content 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" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="right"
        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 sample layout, with each element labelled by its android:id

Step 3 — Using your custom layout:

BackgroundGeolocation.ready({
  app: {
    notification: {
      title: 'The title',
      text: 'The text',
      layout: 'my_notification_layout',
      actions: [
        'notificationButtonFoo',  // <-- register button click-listeners
        'notificationButtonBar'
      ]
    }
  }
});

// Listen to custom notification button clicks (app.notification.actions)
BackgroundGeolocation.onNotificationAction((buttonId) => {
  console.log('[onNotificationAction] - ', buttonId);
  switch (buttonId) {
    case 'notificationButtonFoo':
      // Handle button click on [Foo]
      break;
    case 'notificationButtonBar':
      // Handle button click on [Bar]
      break;
  }
});

If your layout declares custom <TextView /> elements, add app.notification.strings for them as shown above. If onNotificationAction throws, update @transistorsoft/background-geolocation-types (see the known issue above).

Troubleshooting

When it can't resolve part of your notification configuration, the SDK logs a warning or error to logcat ($ adb logcat -s TSLocationManager). These appear when logger.logLevel is Warning or higher; at the default, Off, nothing is logged:

Message Cause
Could not find custom notification layout '<layout>' in app/src/main/res/layout No layout file matches app.notification.layout. The SDK shows its default notification instead.
Failed to find TextView resource: <id> A key in app.notification.strings doesn't match an android:id in your layout.
Failed to find Button resource in notification_layout for notification-action: <id> An entry in app.notification.actions doesn't match an android:id in your layout.
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 bitmap resource matches app.notification.largeIcon, or it names a vector/XML drawable.
Failed to resolve app.notification.smallIcon: <name> No drawable or mipmap resource matches app.notification.smallIcon. The SDK uses your app icon instead.

⚠️ If your release build sets shrinkResources true, Android's resource shrinker can't see a layout that is referenced only by name from JavaScript, and may remove it. The same applies to an icon that only smallIcon or largeIcon names. Keep them with a tools:keep rule in a file with a name of your own, for example android/app/src/main/res/raw/my_notification_keep.xml. Don't name it keep.xml: the SDK ships its own res/raw/keep.xml, and an app file with the same name replaces it.

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

Clone this wiki locally