React Native

This guide discusses how to set up your React Native app to get push notifications from Stream for the non-ringing calls that your user will receive.

Add push provider credentials to Stream

Please follow the below guides for adding appropriate push providers to Stream:

Install Dependencies

Terminal
yarn add @react-native-firebase/app
yarn add @react-native-firebase/messaging
yarn add @notifee/react-native
yarn add @react-native-community/push-notification-ios
npx pod-install

So what did we install precisely?

  • @react-native-firebase/app and @react-native-firebase/messaging for handling incoming Firebase Cloud Messaging notifications on Android.
  • @notifee/react-native - is used to customize and display push notifications.
  • @react-native-community/push-notification-ios for handling Apple Push Notification service (APNs) notifications on iOS.

Android-specific setup

  1. To create a Firebase project, go to the Firebase console and click on Add project.

  2. In the console, click the setting icon next to Project overview and open Project settings. Then, under Your apps, click the Android icon to open Add Firebase to your Android app and follow the steps. Make sure that the Android package name you enter is the same as the value of android.package from your app.json.

  3. After registering the app, download the google-services.json file and place it inside of your project at the following location: /android/app/google-services.json.

  4. To allow Firebase on Android to use the credentials, the google-services plugin must be enabled on the project. This requires modification to two files in the Android directory. Add the highlighted lines in the relevant files:

/android/build.gradle
buildscript {
  dependencies {
    // ... other dependencies
    classpath 'com.google.gms:google-services:4.3.15'
  }
}
/android/build.gradle
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'

The google-services.json file contains unique and non-secret identifiers of your Firebase project. For more information, see Understand Firebase Projects.

iOS-specific setup

Disable Firebase installation

We don’t use Firebase cloud messaging for iOS in the SDK. Unless Firebase is used for other purposes in your app, you can safely remove it from being installed by iOS and avoid the auto-linking. To do that create a file named react-native.config.js in the root of your project and add the following contents:

react-native.config.js
module.exports = {
  dependencies: {
    '@react-native-firebase/app': {
      platforms: {
        ios: null,
      },
    },
    '@react-native-firebase/messaging': {
      platforms: {
        ios: null,
      },
    },
  },
};

Once this is done, pod install must be run again to remove the installed pods.

Add background modes

In Xcode: Open Info.plist file and add the following in UIBackgroundModes. By editing this file with the text editor, you should see:

<key>UIBackgroundModes</key>
<array>
  <string>remote-notification</string>
</array>

Enable push notifications capability

Enable the Push Notifications capability in the Xcode Project > Signing & Capabilities pane.

Update AppDelegate.h

At the top of the file, add:

#import <UserNotifications/UNUserNotificationCenter.h>

Then, add the UNUserNotificationCenterDelegate:

For React-Native v0.71 and above:

@interface AppDelegate : RCTAppDelegate <UNUserNotificationCenterDelegate>

For React-Native v0.70 and below:

@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, UNUserNotificationCen

Update AppDelegate.m or AppDelegate.mm

Add headers

At the top of the file, add:

#import <UserNotifications/UserNotifications.h>
#import <RNCPushNotificationIOS.h>

Add methods

Then add the following methods:

// Required for the register event.
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
 [RNCPushNotificationIOS didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
// Required for the notification event. You must call the completion handler after handling the remote notification.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
  [RNCPushNotificationIOS didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}
// Required for the registrationError event.
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
 [RNCPushNotificationIOS didFailToRegisterForRemoteNotificationsWithError:error];
}
// Required for localNotification event
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
         withCompletionHandler:(void (^)(void))completionHandler
{
  [RNCPushNotificationIOS didReceiveNotificationResponse:response];
}
//Called when a notification is delivered to a foreground app.
-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
{
  completionHandler(UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge);
}

Then add the following lines to didFinishLaunchingWithOptions:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
  center.delegate = self;
  // ...rest
}

Enable push notifications

To receive push notifications, enable the Push Notifications capability in the Xcode Project > Signing & Capabilities pane.

Add Push message handlers

To process the incoming push notifications, the SDK provides the utility functions that you must add to your existing or new notification listeners.

Add callbacks to process notifications and displaying it

When Firebase sends a push message, it must be processed first. For this we expose handler function from the SDK which reads the message and displays it using the @notifee/react-native library. Below is the snippet to add message handlers:

src/utils/setPushMessageHandlers.ts
import messaging from '@react-native-firebase/messaging';
import {
  isFirebaseStreamVideoMessage,
  firebaseDataHandler,
} from '@stream-io/video-react-native-sdk';

export const setFirebaseListeners = () => {
  // Set up the background message handler for Android
  messaging().setBackgroundMessageHandler(async (msg) => {
    if (isFirebaseStreamVideoMessage(msg)) {
      await firebaseDataHandler(msg.data);
    } else {
      // your other messages (if any)
    }
  });
  // Set up the foreground message handler for Android
  messaging().onMessage((msg) => {
    if (isFirebaseStreamVideoMessage(msg)) {
      firebaseDataHandler(msg.data);
    } else {
      // your other messages (if any)
    }
  });
};

The Firebase message handlers

  • The isFirebaseStreamVideoMessage method is used to check if this push message is a video related message. And only this needs to be processed by the SDK.
  • The firebaseDataHandler method is the callback to be invoked to process the message. This callback reads the message and uses the @notifee/react-native library to display push notifications.

If you had disabled the installation of Firebase on iOS, add the above method only for Android using the Platform-specific extensions for React Native.

For example, say you add the following files in your project:

setFirebaseListeners.android.ts
setFirebaseListeners.ts

The method above must only be added to the file that .android extension. The other file must add the method but do nothing like below:

setPushMessageListeners.ts
export const setFirebaseListeners = () => {
  // do nothing
};

This is to ensure that @react-native-firebase/messaging is only imported on the Android platform.

Add notification onPress listeners

Below is the snippet of how to add the notification onPress listeners for Android using @notifee/react-native library:

src/utils/setNotifeeListeners.ts
import {
  isNotifeeStreamVideoEvent,
  onAndroidNotifeeEvent,
} from '@stream-io/video-react-native-sdk';
import { Platform } from 'react-native';
import notifee from '@notifee/react-native';

export const setNotifeeListeners = () => {
  // on press handlers of background notifications for Android
  notifee.onBackgroundEvent(async (event) => {
    if (isNotifeeStreamVideoEvent(event)) {
      await onAndroidNotifeeEvent({ event, isBackground: true });
    } else {
      // your other notifications (if any)
    }
  });
  // on press handlers of foreground notifications for Android
  notifee.onForegroundEvent((event) => {
    if (isNotifeeStreamVideoEvent(event)) {
      onAndroidNotifeeEvent({ event, isBackground: false });
    } else {
      // your other notifications (if any)
    }
  });
};

The Notifee event handlers

  • The isNotifeeStreamVideoEvent method is used to check if the event was a video related notifee event. And only this needs to be processed by the SDK.
  • The onAndroidNotifeeEvent method is the callback to be invoked to process the event. This callback reads the event and makes sure that the call is accepted or declined.

Adding handler for iOS

Below is the snippet of how to add the notification onPress listeners for iOS using @react-native-community/push-notification-ios library. Add the following useEffect in the root component of your App, this is most likely in App.tsx.

import PushNotificationIOS from '@react-native-community/push-notification-ios';
import {
  isPushNotificationiOSStreamVideoEvent,
  onPushNotificationiOSStreamVideoEvent,
} from '@stream-io/video-react-native-sdk';

useEffect(() => {
  PushNotificationIOS.addEventListener('notification', (notification) => {
    if (isPushNotificationiOSStreamVideoEvent(notification)) {
      onPushNotificationiOSStreamVideoEvent(notification);
    } else {
      // any other APN notifications
    }
  });
  return () => {
    PushNotificationIOS.removeEventListener('notification');
  };
}, []);

Setup the push config for the SDK

The SDK automatically processes the incoming push notifications once the setup above is done if the push config has been set using StreamVideoRN.setPushConfig. To do this follow the steps below,

Add the ability to statically navigate to screens in your app

When a user taps on the push notification and the JS engine is not ready, they should still be able to navigate to the screen that shows the active call. You can achieve this by adding the ability to navigate without the navigation property in the react-navigation library.

The following is an example implementation of a utility file that has helpers to statically navigate in the app:

src/utils/staticNavigation.ts
import { createNavigationContainerRef } from '@react-navigation/native';

import { RootStackParamList } from '../navigation/types';

export const navigationRef = createNavigationContainerRef<RootStackParamList>();

/**
 * This is used to run the navigation logic from root level even before the navigation is ready
 */
export const staticNavigate = (
  ...navigationArgs: Parameters<typeof navigationRef.navigate>
) => {
  // note the use of setInterval, it is responsible for constantly checking if requirements are met and then navigating
  const intervalId = setInterval(async () => {
    // run only when the navigation is ready and add any other requirements (like authentication)
    if (navigationRef.isReady() && GlobalState.hasAuthentication) {
      clearInterval(intervalId);
      navigationRef.navigate(...navigationArgs);
    }
  }, 300);
};

When doing this it is very important to set the navigationRef in your navigation container as shown below:

import { navigationRef } from './src/utils/staticNavigationUtils';

<NavigationContainer ref={navigationRef}>
  <MyAppNavigator />
</NavigationContainer>;

Setup the push config

Once we have set up the methods to navigate the app from a static method we are ready to call the StreamVideoRN.setPushConfig method. Below is an example of how this method can be called,

src/utils/setPushConfig.ts
import {
  StreamVideoClient,
  StreamVideoRN,
} from '@stream-io/video-react-native-sdk';
import { AndroidImportance } from '@notifee/react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { STREAM_API_KEY } from '../../constants';
import { staticNavigate } from './staticNavigationUtils';

export function setPushConfig() {
  StreamVideoRN.setPushConfig({
    ios: {
      // add your push_provider_name for iOS that you have setup in Stream dashboard
      pushProviderName: __DEV__ ? 'apn-video-staging' : 'apn-video-production',
    },
    android: {
      // add your push_provider_name for Android that you have setup in Stream dashboard
      pushProviderName: __DEV__
        ? 'firebase-video-staging'
        : 'firebase-video-production',
      // configure the notification channel to be used for non ringing calls for Android.
      callChannel: {
        id: 'stream_call_notifications',
        name: 'Call notifications',
        // This importance will ensure that the notification will appear on-top-of applications.
        importance: AndroidImportance.HIGH,
        sound: "default",
      },
      // configure the functions to create the texts shown in the notification
      // for non ringing calls in Android.
      callNotificationTextGetters: {
        getTitle(type, createdUserName) {
          if (type === 'call.live_started') {
            return `Call went live, it was started by ${createdUserName}`;
          } else {
            return `${createdUserName} is notifying you about a call`;
          }
        },
        getBody(_type, createdUserName) {
          return 'Tap to open the call';
        },
      },
    },
    // optional: add the callback to be executed when a non ringing call notification is tapped
    onTapNonRingingCallNotification: () => {
      const [callType, callId] = call_cid.split(':');
      if (callType === 'livestream') {
        staticNavigate({ name: 'LiveStreamCallScreen', params: undefined });
      } else {
        staticNavigate({ name: 'ActiveCallScreen', params: undefined });
      }
    },
    // add the async callback to create a video client
    // for incoming calls in the background on a push notification
    createStreamVideoClient: async () => {
      // note that since the method is async,
      // you can call your server to get the user data or token or retrieve from offline storage.
      const userId = await AsyncStorage.getItem('@userId');
      const userName = await AsyncStorage.getItem('@userName');
      if (!userId) return undefined;
      // an example promise to fetch token from your server
      const tokenProvider = () => yourServer.getTokenForUser(userId).then((auth) => auth.token);
      const user = { id: userId, name: userName };
      return StreamVideoClient.getOrCreateInstance({
        apiKey: STREAM_API_KEY, // pass your stream api key
        user,
        tokenProvider,
      });
    },
  });
}

Call the created methods outside of the application lifecycle

Call the methods we have created outside of your application cycle. That is, alongside your AppRegistry.registerComponent() method call at the entry point of your application code. This is because the app can be opened from a dead state through a push notification and in that case, we need to use the config as soon as the JS bridge is initialized.

Following is an example,

index.js
import { AppRegistry } from 'react-native';
import { setPushConfig } from 'src/utils/setPushConfig';
import { setNotifeeListeners } from 'src/utils/setNotifeeListeners';
import { setFirebaseListeners } from 'src/utils/setFirebaseListeners';
import App from './App';

setPushConfig();
setNotifeeListeners();
setFirebaseListeners();
AppRegistry.registerComponent('app', () => App);

Request for notification permissions

At an appropriate place in your app, request for notification permissions from the user. Below is a small example of how to request permissions using react-native-permissions library:

import { requestNotifications } from 'react-native-permissions';

await requestNotifications(['alert', 'sound']);

Disabling push - usually on logout

In some cases you would want to disable push from happening. For example, if user logs out of your app. Or if the user switches. You can disable push like below:

import { StreamVideoRN } from '@stream-io/video-react-native-sdk';

await StreamVideoRN.onPushLogout();

Troubleshooting

  • During development, you may be facing a situation where push notification is shown but its events like accepting or rejecting a call don’t work. This is because, during hot module reloading the global event listeners may get de-registered. To properly test during development, make sure that you fully restart the app or test in release mode without the metro packager.
  • You can check the “Webhook & Push Logs” section in the Stream Dashboard to see if Notifications were sent by Stream.
  • If you are still having trouble with Push Notifications, please submit a ticket to us at support.

Closed notification behavior on Android

On Android, users can set certain OS-level settings, usually revolving around performance and battery optimization, that can prevent notifications from being delivered when the app is in a killed state. For example, one such setting is the Deep Clear option on OnePlus devices using Android 9 and lower versions.

© Getstream.io, Inc. All Rights Reserved.