Jacka
Jacka

Reputation: 2560

React Native - How to open route from push notification

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:

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.

Upvotes: 23

Views: 40449

Answers (4)

Sanchitos
Sanchitos

Reputation: 8591

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;

Upvotes: 0

Jim
Jim

Reputation: 2322

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>
        );
    }
}

Upvotes: 0

Anmol Sharma
Anmol Sharma

Reputation: 221

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

Upvotes: 14

Jacka
Jacka

Reputation: 2560

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
  }

}

Upvotes: 19

Related Questions