23

I'm using react-navigation and react-native-push-notification. How can I open a certain StackNavigator's screen in onNotification callback? Should work when:

  • app is closed
  • app is in the foreground
  • app is in the background

I only need it working in Android for now.

I've tried to pass a callback function to notification in my component:

_handleClick() {
  PushNotification.localNotification({
    foreground: false
    userInteraction: false
    message: 'My Notification Message'
    onOpen: () => { this.props.navigation.navigate("OtherScreen") },
  })
}

And to fire onOpen in PushNotification config:

onNotification: function(notification) {
   notification.onOpen()
}

But it seems that functions can't be passed to notification, unless a value is a string it's ignored, causing onOpen to be undefined.

Jacka
  • 2,270
  • 4
  • 27
  • 34

4 Answers4

19

Okay, it seems like I gotta post my own solution :)

// src/services/push-notification.js
const PushNotification = require('react-native-push-notification')

export function setupPushNotification(handleNotification) {
  PushNotification.configure({

      onNotification: function(notification) {
        handleNotification(notification)
      },

      popInitialNotification: true,
      requestPermissions: true,
  })

  return PushNotification
}


// Some notification-scheduling component
import {setupPushNotification} from "src/services/push-notification"

class SomeComponent extends PureComponent {

  componentDidMount() {
    this.pushNotification = setupPushNotification(this._handleNotificationOpen)
  }

  _handleNotificationOpen = () => {
    const {navigate} = this.props.navigation
    navigate("SomeOtherScreen")
  }

  _handlePress = () => {
    this.pushNotification.localNotificationSchedule({
      message: 'Some message',
      date: new Date(Date.now() + (10 * 1000)), // to schedule it in 10 secs in my case
    })

  }

  render() {
    // use _handlePress function somewhere to schedule notification
  }

}
Jacka
  • 2,270
  • 4
  • 27
  • 34
  • 2
    I recommend reading this: https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html – izikandrw Jun 25 '18 at 21:54
  • can you check this Q please, @Jacka https://stackoverflow.com/questions/57171787/navigate-to-screen-after-opening-a-notification – DevAS Jul 23 '19 at 22:25
  • 1
    This is not working with Auth flow (from react navigation doc : https://reactnavigation.org/docs/en/auth-flow.html). Because 2 navigate can occurred at 1st launch and there is no guarantee that navigate from push notification is the last call... – Aure77 Jan 21 '20 at 18:41
  • 1
    it works but it needs to be a singleton, and every time when view is called, the process will be stacked – elporfirio Jul 08 '21 at 23:10
12

This solution I found over the official website of Firebase and this seems to be the best example/sample work for this. Below is the sample snippet and also the link attached. Hope it help others.

import React, { useState, useEffect } from 'react';
import messaging from '@react-native-firebase/messaging';
import { NavigationContainer, useNavigation } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

const Stack = createStackNavigator();

function App() {
  const navigation = useNavigation();
  const [loading, setLoading] = useState(true);
  const [initialRoute, setInitialRoute] = useState('Home');

  useEffect(() => {
    // Assume a message-notification contains a "type" property in the data payload of the screen to open

    messaging().onNotificationOpenedApp(remoteMessage => {
      console.log(
        'Notification caused app to open from background state:',
        remoteMessage.notification,
      );
      navigation.navigate(remoteMessage.data.type);
    });

    // Check whether an initial notification is available
    messaging()
      .getInitialNotification()
      .then(remoteMessage => {
        if (remoteMessage) {
          console.log(
            'Notification caused app to open from quit state:',
            remoteMessage.notification,
          );
          setInitialRoute(remoteMessage.data.type); // e.g. "Settings"
        }
        setLoading(false);
      });
  }, []);

  if (loading) {
    return null;
  }

  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName={initialRoute}>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Settings" component={SettingsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Link: https://rnfirebase.io/messaging/notifications#handling-interaction

Anmol Sharma
  • 201
  • 3
  • 9
  • This doesn't allow passing parameters when the app is opened from quit state. In the onNotificationOpenedApp() handler, we use navigation.navigate() which allows passing parameters as second argument. However when we set initial route when app is opened from quit state, there's no way to pass parameters to the screen/route – Arion_Miles Jul 12 '20 at 09:20
  • @Arion_Miles you can use the below method to get the quit/background state data Register background handler:- messaging().setBackgroundMessageHandler(async remoteMessage => { console.log('Message handled in the background!', remoteMessage); }); – Anmol Sharma Jul 13 '20 at 10:07
  • The background handler is defined in index.js though, as the rnfirebase.io doc recommends setting it at the earliest possible time in your app. My getInitialNotification() is defined in App.js and when a notification is opened while app is quit, everything defined in getInitialNotification handler is executed, which is where I must add code for redirecting my user to appropriate screen with the notification data. Let me know if I'm not making sense. – Arion_Miles Jul 13 '20 at 14:01
  • @Arion_Miles Well from my side you must check from which function you are getting the data according to your requirement. In my situation, I wanted to get the data once the notification is been opened and then navigate according to that and it was gracefully handled by onNotificationOpenedApp function. – Anmol Sharma Jul 27 '20 at 05:34
  • 1
    this is a valuable answer if using `react-native-firebase` v6 – Jim Sep 05 '20 at 17:02
0

Seeing as how im using the legacy react-native-firebase I figured id post my solution for this issue, given that its slightly different than one of the above answers which utilizes RN-firebase V6. My solution is only slightly different, this solution works for notification handling with react-native-firebase v5.x :

import * as React from 'react';
import { Text, TextInput } from 'react-native';
import AppNavigation from './src/navigation';
import { Provider } from 'react-redux';
import { store, persistor } from './src/store/index.js';
import 'react-native-gesture-handler';
import firebase from 'react-native-firebase';
import { PersistGate } from 'redux-persist/integration/react';

export default class App extends React.Component {
    constructor(props) {
        super(props);
        if (firebase.apps.length === 0) {
            firebase.initializeApp({});
        }
    }

    async componentDidMount() {
        // Initialize listener for when a notification has been displayed
        this.removeNotificationDisplayedListener = firebase.notifications().onNotificationDisplayed((notification) => {
            // process notification as required... android remote notif's do not have a "channel ID".
        });

        // Initialize listener for when a notification is received
        this.removeNotificationListener = firebase.notifications().onNotification((notification) => {
            // Process notification
        });

        // Listener for notification tap if in FOREGROUND & BACKGROUND
        this.removeNotificationOpenedListener = firebase.notifications().onNotificationOpened((notificationOpen) => {
            // get the action trigger by the notification being opened
            const action = notificationOpen.action;

            // get info about the opened notification
            const info = notificationOpen.notification;

            // log for testing
            console.log('ACTION => ' + action + '\nNOTIFICATION INFO => ' + JSON.stringify(info));
        });

        // Listener for notification tap if app closed
        const notificationOpen = await firebase.notifications().getInitialNotification();
        if (notificationOpen) {
            // App was opened by notification
            const action = notificationOpen.action;
            const info = notificationOpen.notification;

            // log for testing:
            console.log('ACTION => ' + action + '\nNOTIFICATION INFO => ' + JSON.stringify(info));
        }
    }

    componentWillUnmount() {
        // Invoke these functions to un-subscribe the listener
        this.removeNotificationDisplayedListener();
        this.removeNotificationListener();
        this.removeNotificationOpenedListener();
    }

    render() {
        return (
            <Provider store={store}>
                <PersistGate loading={null} persistor={persistor}>
                    <AppNavigation />
                </PersistGate>
            </Provider>
        );
    }
}
Jim
  • 1,988
  • 6
  • 34
  • 68
0

This information may be useful to someone.

In my scenario, if a user receives a notification but hasn't logged in, then the app should not redirect them to the desired screen.

If the user is not on the Login screen, and user press the notification, we should redirect them (Screen2 in my case).

For this I send the navigation reference to my NotificationHelper

Using:

@react-navigation/native: 6.X

@react-native-firebase/messaging: 17.x

@notifee/react-native: 7.x

Code:

MainScreen.tsx

import { createNavigationContainerRef } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

const MainScreen: React.FC = () => {
  const Stack = createNativeStackNavigator();
  const navigationRef = createNavigationContainerRef();
  return (
    <NavigationContainer ref={navigationRef}>
      <Stack.Navigator screenOptions={{ headerShown: false }}>
        <Stack.Screen name="Login" component={LoginScree} />
        <Stack.Screen name="Screen1" component={Screen1} />
        <Stack.Screen
          name="Screen2"
          component={Screen2}
        />
      </Stack.Navigator>
      <NotificationHelper navigationRef={navigationRef} />
    </NavigationContainer>     
  );
};

NotificationHelper.tsx

import messaging, {
  FirebaseMessagingTypes,
} from '@react-native-firebase/messaging';
import { NavigationContainerRefWithCurrent } from '@react-navigation/native';
import { FC, useCallback, useEffect } from 'react';

type PropsType = {
  navigationRef: NavigationContainerRefWithCurrent<ReactNavigation.RootParamList>;
};

const NotificationHelper: FC<PropsType> = (props: PropsType) => {
  const redirectToScreen = useCallback(
    (notification: any) => {
      if (props.navigationRef) {
        const currentRoute = props.navigationRef.getCurrentRoute()?.name;
        if (currentRoute) {
          if (currentRoute !== 'Login') {
            props.navigationRef.navigate('Screen2', {
              param1: notification.property1,
              param2: notification.property2,
            });
          }
        }
      }
    },
    [props.navigationRef],
  );

  useEffect(() => {
    const unsubscribeOpenedApp = messaging().onNotificationOpenedApp(
      async (remoteMessage) => {
        if (remoteMessage.data) {
          console.debug('User pressed notification');
          redirectToScreen(remoteMessage.data);
        }
      },
    );
    const unsubscribeForegroundOpenApp = notifee.onForegroundEvent(
      ({ type, detail }) => {
        switch (type) {
          case EventType.PRESS:
            console.debug('User pressed notification');
            if (detail.notification && detail.notification.data) {
              redirectToScreen(
                detail.notification.data,
              );
            }
            break;
        }
      },
    );
    return () => {
      unsubscribeOpenedApp();
      unsubscribeForegroundOpenApp();
    };
  }, [redirectToScreen]);

  return null;
};

export default NotificationHelper;
Sanchitos
  • 8,423
  • 6
  • 52
  • 52