# Firebase Messaging Service overrides

## The problem

The `@stream-io/react-native-callingx` package provides its own `FirebaseMessagingService` implementation (`StreamMessagingService`) and registers it in its manifest.

**Android FCM only delivers each push to a single `FirebaseMessagingService` per app.** If your merged manifest declares more than one — typically because another push SDK registers its own, or because your app ships a custom service — only the one PackageManager resolves first actually fires. Depending on which service wins the merge, you'll see either Stream ringing pushes stop showing or the other SDK stop receiving its pushes.

>
> **Note:** The manifest merger doesn't flag this as an error — declaring multiple `MESSAGING_EVENT` services is legal and the build succeeds. There is just no fan-out at runtime.
>

## How to detect the conflict

In Android Studio, open `AndroidManifest.xml` and switch to the **Merged Manifest** tab. Search for `MESSAGING_EVENT` and look at the `<service>` entries that declare it.

Firebase's own default `FirebaseMessagingService` always appears here — that's expected and harmless, since Android's priority rules keep it from overriding your service. You have a conflict when another service (another push SDK, or your own) also declares `MESSAGING_EVENT`, because FCM delivers each push to only one service.

Pick the section for your workflow: [**React Native CLI**](#react-native-cli) if you write native code and manage the manifest yourself, or [**Expo**](#expo) if you use the managed workflow (`expo prebuild`).

## React Native CLI

Two patterns resolve this — pick based on whether you can subclass `StreamMessagingService` or need to host your own service.

### Option 1 — Subclass `StreamMessagingService`

Pick this when you don't need to inherit from another SDK's service. Subclassing handles Stream's `call.ring` push notifications automatically, keeps the React Native Firebase background-message flow intact, and gives you a hook for custom forwarding.

```kotlin title="android/app/src/main/java/.../AppMessagingService.kt"
package com.example.app

import com.google.firebase.messaging.RemoteMessage
import io.getstream.rn.callingx.StreamMessagingService

class AppMessagingService : StreamMessagingService() {

  override fun onMessageReceived(remoteMessage: RemoteMessage) {
    // handles Stream call.ring
    super.onMessageReceived(remoteMessage)

    // Optional — short-circuit other forwarders for Stream pushes.
    // `isStreamCallRing` is provided for flexibility; Stream's `call.ring`
    // has already been handled by `super.onMessageReceived(...)`.
    if (StreamMessagingHelper.isStreamCallRing(remoteMessage)) return

    // forward non-Stream pushes to other SDKs
    ExampleSDK.passRemoteMessage(applicationContext, remoteMessage)
  }
}
```

### Option 2 — Forward via `StreamMessagingHelper`

Pick this when your app already hosts its own `FirebaseMessagingService`. Remove Stream's service from the manifest and forward `call.ring` payloads via `StreamMessagingHelper` from your own service.

```kotlin title="android/app/src/main/java/.../AppMessagingService.kt"
package com.example.app

import com.google.firebase.messaging.RemoteMessage
import io.getstream.rn.callingx.StreamMessagingHelper
import io.invertase.firebase.messaging.ReactNativeFirebaseMessagingService

class AppMessagingService : ReactNativeFirebaseMessagingService() {

  override fun onMessageReceived(remoteMessage: RemoteMessage) {
    // chain to the base service
    super.onMessageReceived(remoteMessage)

    // Optional gate — provided for finer control over the forwarding flow.
    // `handleMessage` is also safe to call unconditionally; it no-ops for
    // payloads that aren't a Stream `call.ring`.
    if (StreamMessagingHelper.isStreamCallRing(remoteMessage)) {
      StreamMessagingHelper.handleMessage(applicationContext, remoteMessage)
    } else {
      // forward to other SDKs
      ExampleSDK.passRemoteMessage(applicationContext, remoteMessage)
    }
  }
}
```

>
> **Note:** React Native Firebase delivers messages to JavaScript through its own broadcast receiver, so `onMessage` and `setBackgroundMessageHandler()` keep firing regardless of which service you register. Extending `ReactNativeFirebaseMessagingService` (as both options do) only matters for token refresh — see [Forward token refresh](#forward-token-refresh).
>

The helper exposes these methods:

| API                                                           | Purpose                                                                                                                             |
| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `StreamMessagingHelper.isStreamCallRing(remoteMessage)`       | Returns `true` if the payload is a Stream Video `call.ring`. Useful when you want to short-circuit other SDK forwarders.            |
| `StreamMessagingHelper.handleMessage(context, remoteMessage)` | Handles a Stream `call.ring` payload (starts the incoming call flow). No-op for non-Stream payloads — safe to call unconditionally. |
| `StreamMessagingHelper.forwardNewToken(token)`                | Forwards a refreshed FCM token so Stream can re-register the device.                                                                |

### Forward token refresh

If your service extends `ReactNativeFirebaseMessagingService` (including Stream's `StreamMessagingService`), token refresh already works — no extra step.

If it extends a different service, forward the token so Stream can re-register the device:

```kotlin title="android/app/src/main/java/.../AppMessagingService.kt"
override fun onNewToken(token: String) {
  super.onNewToken(token)
  StreamMessagingHelper.forwardNewToken(token)
}
```

### Register your service in the manifest

Both options need the same change in `android/app/src/main/AndroidManifest.xml` — remove the default `StreamMessagingService` and register yours in its place:

```xml title="android/app/src/main/AndroidManifest.xml"
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
  <application>
    <service
        android:name="io.getstream.rn.callingx.StreamMessagingService"
        tools:node="remove" />

    <service
        android:name=".AppMessagingService"
        android:exported="false">
      <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
      </intent-filter>
    </service>
  </application>
</manifest>
```

## Expo

On the Expo managed workflow you don't write native Kotlin or edit `AndroidManifest.xml` yourself, so the [React Native CLI](#react-native-cli) options above don't apply. The config plugin resolves the conflict through the optional `androidMessagingServiceBaseClass` property:

| Value                            | Behavior                                                                                                                                 |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| **omitted**                      | Automatically overrides `expo-notifications`' service when it's installed; does nothing otherwise.                                       |
| **`null`**                       | Opt out — no override, even when `expo-notifications` is installed.                                                                      |
| **a fully-qualified class name** | Override that specific `FirebaseMessagingService` instead, e.g. `"io.invertase.firebase.messaging.ReactNativeFirebaseMessagingService"`. |

So **by default, when `expo-notifications` is installed and `ringing` is enabled, the plugin overrides its `FirebaseMessagingService` automatically** — you don't need to set anything. Set the property only to override a different service, or to `null` to opt out.

To override a specific service, set the property to that service's class name:

```json title="app.json"
{
  "plugins": [
    [
      "@stream-io/video-react-native-sdk",
      {
        "ringing": true,
        "androidMessagingServiceBaseClass": "com.example.app.CustomFirebaseMessagingService"
      }
    ]
  ]
}
```

When an override is applied, on `expo prebuild` the plugin:

- generates a `StreamVideoMessagingService` that extends the base class and handles Stream's `call.ring` pushes,
- registers it as your app's `com.google.firebase.MESSAGING_EVENT` handler,
- removes both Stream's default `StreamMessagingService` and the base class's own service registration, so the generated service is the one that receives pushes,
- forwards token refreshes to Stream when the base isn't React Native Firebase, so device registration keeps working automatically — no action needed on your side.

Stream's `call.ring` is handled natively and **not** forwarded to the base class (so there's no duplicate call notification). Every other push is forwarded via `super.onMessageReceived(...)`, so the SDK you extend (for example `expo-notifications`) keeps receiving its notifications.

>
> **Note:** Use the **fully-qualified** class name including its package — for example `com.example.app.CustomFirebaseMessagingService`, not just `CustomFirebaseMessagingService`.
>

>
> **Warning:** When you change this property — for example set it to `null` to opt out, or switch to a different base — run `expo prebuild --clean`. A plain `expo prebuild` won't remove the previously generated service, manifest entries, or Gradle dependency, so the earlier override would stay in effect.
>

>
> **Note:** The SDK handles `call.ring` notifications for you — the ringing flow is fully managed natively. There's nothing you need to do with that notification type for ringing to work, so you can skip it in your own notification handling.
>

---

For the most recent version of this documentation, visit [https://getstream.io/video/docs/react-native/advanced/incoming-calls/advanced/firebase-messaging-conflicts/](https://getstream.io/video/docs/react-native/advanced/incoming-calls/advanced/firebase-messaging-conflicts/).