0

I am implementing FCM notifications in an Ionic React application. I am having trouble navigating to another page to display the notification details.

I have created a FCMService class in my react App, and initialising this in the index.ts file.

// FCMService.ts

export default class FCMService {

    public static Instance: FCMService;

    private _store: Store<IAppState>;

    constructor(store: Store<IAppState>) {
        this._store = store;
    }

    public static Initalise(store: Store<IAppState>) {
        if (!FCMService.Instance) {
            FCMService.Instance = new FCMService(store);
            FCMService.Instance.InitaliseFCM();

            FCMService.Instance._store.subscribe(() => { console.log(store.getState()) });
        } else {
            console.debug("FCM service already intialised. Please use FCMService.Instance");
        }
    }

    private InitaliseFCM() {
        // Request permission to use push notifications
        // iOS will prompt user and return if they granted permission or not
        // Android will just grant without prompting
        PushNotifications.requestPermission().then(result => {
            console.log(result);
            if (result.granted) {
                // Register with Apple / Google to receive push via APNS/FCM
                PushNotifications.register();
            } else {
                // Show some error
            }
        });

        // On success, we should be able to receive notifications
        PushNotifications.addListener('registration', (token: PushNotificationToken) => {
            console.log(token);
            localStorage.setItem("FCM_TOKEN", token.value);
        }
        );

        // Some issue with our setup and push will not work
        PushNotifications.addListener('registrationError',
            (error: any) => {
                console.log(error);
            }
        );

        // Show us the notification payload if the app is open on our device
        PushNotifications.addListener('pushNotificationReceived',
            (notification: PushNotification) => {
                console.log(notification);
                let data = notification.notification.data as INotificationData;
            }
        );

        // Method called when tapping on a notification
        PushNotifications.addListener('pushNotificationActionPerformed',
            (notification: PushNotificationActionPerformed) => {
                console.log(notification);
                let data = notification.notification.data as INotificationData;

                this._store.dispatch(setNotificationActionCreator(data));
            }
        );
    }
}

and then the index.ts

const store = configureStore();

interface MainProps {
    store: Store<IAppState>;
}

FCMService.Initalise(store);
ReactDOM.render(<Provider store={store}><App /> </Provider>, document.getElementById('root'));
serviceWorker.unregister();

I even tried using the Redux store to save the notification on Tap - and then that would publish the notification change event (which might of worked - if I could access the useHistory() hook in the App.tsx file)

This was my attempt at navigating via Redux store in App.tsx

const App: React.FC<IProps> = ({ getCompanies, getUser, notification }) => {
  console.log('app');
  console.log(process.env);

    const history = useHistory();
    if(notification){
        history.push(`/page/plot-position/{notification.id}`);
    }

  return (
    <IonApp>
      <IonReactRouter>
        <IonSplitPane contentId="main" when="false">

          <Menu />

          <IonRouterOutlet id="main">
            <Route path="/login" component={LoginPage} exact />
            <PrivateRoute path="/page/plot-position/:notificationId/" component={PlotPositionPage} exact />
            <Redirect from="/" to="/login" exact />
          </IonRouterOutlet>

        </IonSplitPane>
      </IonReactRouter>
    </IonApp>
  );
};

const mapStateToProps = (store: IAppState) => {
  return {
    user: store.user.user as UserDTO,
    notification: store.notificationState.notification
  };
};

const mapDispatchToProps = (dispatch: any) => {
  return {
    getCompanies: () => dispatch(getCompaniesStartActionCreator()),
    getUser: () => dispatch(getUserStartActionCreator())
  }
};

export default connect(mapStateToProps, mapDispatchToProps)(App);
Dawood Awan
  • 7,051
  • 10
  • 56
  • 119
  • 1
    This was my problem before. I ended up using location.href to navigate which doesnt look good... So I ended up using React Native instead. Ionic is very limited. – wobsoriano Jun 22 '20 at 22:09
  • its not clear to me what is happening other than its not working? – Aaron Saunders Jun 28 '20 at 01:26
  • @AaronSaunders when tapping on the Notification when the App is in the background, I want to navigate to another page. But I can't find a way to do this, as the History object is created after the Event is triggered. So I can't do it in App.tsx, neither did it work when using Redux to set the Notification in the store - which in turn should trigger the notification change event – Dawood Awan Jun 28 '20 at 21:35
  • 1
    @DawoodAwan did you find a good resolution to this? I'm running into the same issue... – Nick Sep 03 '20 at 21:07
  • @Nick unfortunately no - so on notification click not doing much - just app launch. please let me know if you do. – Dawood Awan Sep 03 '20 at 23:04
  • How did you get app launching at tap? – SalahAdDin Nov 19 '20 at 14:58
  • @SalahAdDin the App was launching on tap in android (didn't check iOS) - and was triggering the event `pushNotificationActionPerformed` - but from there I couldn't get it to navigate to the route I wanted it too. – Dawood Awan Nov 19 '20 at 15:05
  • How did you get it launching? – SalahAdDin Nov 19 '20 at 15:07
  • @SalahAdDin launching the app just worked out the box for me, didn't have to do anything. Just need to make sure the App has permission for FCM Notifications `PushNotifications.requestPermission()` – Dawood Awan Nov 19 '20 at 15:12
  • @SalahAdDin this is the article I followed - (https://medium.com/enappd/firebase-push-notification-in-ionic-react-app-using-capacitor-b6726c71bda4) – Dawood Awan Nov 19 '20 at 15:16

2 Answers2

2

It looks like your navigation works, but you're having trouble passing the notification object through to the page? You can pass the object through history state.

To access the useHistory hook you would need to make your FCMService a custom hook.

const useFCMService = (): void => {
  const history = useHistory();
  React.useEffect(() => {
    // Method called when tapping on a notification
    PushNotifications.addListener('pushNotificationActionPerformed', 
      (action: PushNotificationActionPerformed) => {
        const notification = action.notification.data as INotificationData;
        history.push({ pathname: '/page/plot-position/', state: { notification } });
      }
    );
  }, []);
}

And then include your useFCMService custom hook in your App component.

const App: React.FC<IProps> = ({ getCompanies, getUser }) => {
  useFCMService();
  ...
};
1

Deep linking provides us a way to do this: Using both an action to open the application and an action at opening the application we can enroute the user to the correct destination.

Opening the application

Here we will create an action to open the url when the user taps on the push notification; to do this less use a listener:

const {PushNotifications, App} = Plugins

***

PushNotifications.addListener(
        "pushNotificationActionPerformed",
        (notification: PushNotificationActionPerformed) =>{
            const data = notification.notification.data;
            if (data.packageNumber) App.openUrl({url: `com.company.appname://tabs/package-details/${data.packageNumber}`})
            else App.openUrl({url:'/tabs'})
        }
    )

com.company.app:// is of capital importance since the app must reach the application must reach an existing given url, otherwise the following action(catching the url) won't be triggers since it waits a complete true from the App.openUrl function; as we are opening an internal url, this must begin with the apps given name in the capacitor config page(see the following example where we can realize how use the local url).

In this way we are adding a function to open the application in an specific route.

Redirecting the user

Here, we will complete the application's part from the deep linking tutorial: we create a new listener component who handles the appOpenUrl events and redirects to the user and we will put it on the main App file inside of its respective IonRouter:

const AppUrlListener: React.FC<any> = () => {
  let history = useHistory();
  useEffect(() => {
    App.addListener('appUrlOpen', (data: any) => {
      const slug = data.url.split(':/').pop();
      if (slug) {
        history.push(slug);
      }
    });
  }, []);

  return null;
};

Don't forget the route in router must begin with /, and since the application url contains :/, we split the url here and we get the second part, the slug; we push it on the history, triggering the router and getting the normal behaviour when you entering in a new route.

We will add this component inside of the router:

<IonReactRouter>
        <IonSplitPane contentId="main">
          <Menu />
          <AppUrlListener />
          <IonRouterOutlet id="main">

Now, the application will be listening the appOpenUrl event, and when it gets a new of this events, it will push the gotten url to the history, redirecting the user to that route.

SalahAdDin
  • 2,023
  • 25
  • 51