How to unsubscribe react-native-firebase getInitialNotification - javascript

I am using react-native-firebase for push notifications in a react-native application.
When a notification is received, opens the app by clicking on the notification (this works correctly by triggering the getInitialNotification function). However, when i navigate from one screen to another it will be triggered again. How can this be stopped? is there any alternative to unsubscribe this event after it is triggered?
Or is there any workaround to this issue?
class Header extends Component {
async componentDidMount() {
this.checkPermission();
this.createNotificationListeners();
}
componentWillUnmount() {
this.notificationListener();
this.notificationOpenedListener();
}
async checkPermission() {
const enabled = await firebase.messaging().hasPermission();
if (enabled) {
this.getToken();
}
}
async getToken() {
//token retrieve code
}
async createNotificationListeners() {
firebase.messaging().subscribeToTopic("all")
/*
* If your app is closed, you can check if it was opened by a notification being clicked / tapped / opened as follows:
* */
const notificationOpen = await firebase.notifications().getInitialNotification();
if (notificationOpen) {
alert(title, body);
}
}
render() {
return (
<View></View>
);
}
}

Related

Is there any way to get back to same page while canceling an asyncdata api call via browsers's back button

I have been using Nuxtjs and axios for API calls. In a page called post listing, once I select a post which suppose to navigate me to details page, while loading towards the page, once I click on browse's back button and try to get back to same listing page again without visiting the details page. It gives 404 and post not found error.
Is there any good way to prevent that?
Here is the code details page
async asyncData({ $api, route, $auth, error }) {
try {
// console.log(route.params)
const { feed } = await $api.Feed.feedDetails(
route.params.id,
$auth.loggedIn
)
if (route.query.origin === 'channel') {
const { channel } = await $api.Channels.channelDetails(
route.query.cId,
$auth.loggedIn
)
return { feed, channel_details: channel }
} else {
return { feed }
}
} catch (e) {
error({ statusCode: 404, message: 'Feed not found' })
}
},
I have this middleware in the page details page
export default async function (context) {
if (context.$auth.$state.loggedIn) {
const params = {
page: 1,
}
const { threads } = await context.$api.Messages.MessageThreadList(params)
const { notifications } = await context.$api.Users.getNotifications({
last_id: 0,
})
context.store.commit('addThreadList', threads)
context.store.commit('addNotification', notifications)
}
}

Show notification on foreground react native firebase v6

I am using the latest react native version 0.62 and latest version of react-native-firebase i.e. v6. I am able to get the notification and it working fine on the background but its not displaying on foreground.
Here is the screenshot:
And here is my code:
checkPermission = async () => {
const enabled = await messaging().hasPermission();
console.log('enabled ******* ',enabled)
if (enabled) {
this.getFcmToken();
} else {
this.requestPermission();
}
};
getFcmToken = async () => {
const fcmToken = await messaging().getToken();
if (fcmToken) {
console.log('Your Firebase Token is:', fcmToken);
// this.showAlert('Your Firebase Token is:', fcmToken);
} else {
console.log('Failed', 'No token received');
}
};
requestPermission = async () => {
try {
await messaging().requestPermission();
// User has authorised
} catch (error) {
// User has rejected permissions
}
};
messageListener = async () => {
console.log('inside message listener ****** ')
messaging().onMessage(async remoteMessage => {
Alert.alert('A new FCM message arrived!', JSON.stringify(remoteMessage));
};
showAlert = (title, message) => {
Alert.alert(
title,
message,
[{ text: 'OK', onPress: () => console.log('OK Pressed') }],
{ cancelable: false },
);
};
componentDidMount() {
this.checkPermission();
this.messageListener();
}
By default rnfirebase not supporting displaying notification popup when app is in foreground state as they mentioned here. So push notification pop up only displayed when app is in background state or closed.
So if you want to display push notification on foreground mode also then you have to use extra library which will be display fired push notification as local notification as mention in their documentation.
If the RemoteMessage payload contains a notification property when sent to the onMessage handler, the device will not show any notification to the user. Instead, you could trigger a local notification or update the in-app UI to signal a new notification.
So as a solution you can use react-native-push-notification to fire push notification when app in foreground.
To do so, just install it by command :
npm i react-native-push-notification
For android you don't need to follow any native installation steps just install library by this command and then you can fire local push notification as below :
Create a file called NotificationController.android.js :
import React, { useEffect } from 'react';
import { Alert } from 'react-native';
import messaging from '#react-native-firebase/messaging';
import PushNotification from 'react-native-push-notification';
const NotificationController = (props) => {
useEffect(() => {
const unsubscribe = messaging().onMessage(async (remoteMessage) => {
PushNotification.localNotification({
message: remoteMessage.notification.body,
title: remoteMessage.notification.title,
bigPictureUrl: remoteMessage.notification.android.imageUrl,
smallIcon: remoteMessage.notification.android.imageUrl,
});
});
return unsubscribe;
}, []);
return null;
};
export default NotificationController;
Now, when app is in foreground state and if onMessage receive any message from firebase then PushNotification will fire local notification.
Update: For iOS
For iOS you have to install #react-native-community/push-notification-ios using this command:
npm i #react-native-community/push-notification-ios
Also follow all the native installation steps as suggested in document.
Then you can create file called NotificationController.ios.js where you can handle notification for iOS.
import { useEffect } from 'react';
import { Alert } from 'react-native';
import messaging from '#react-native-firebase/messaging';
import PushNotification from 'react-native-push-notification';
import PushNotificationIos from '#react-native-community/push-notification-ios';
const NotificationController = (props) => {
const navigation = useNavigation();
// Called when application is open by clicking on notification
// and called when application is already opend and user click on notification
PushNotification.configure({
onNotification: (notification) => {
if (notification) {
console.log(notification);
Alert.alert('Opened push notification', JSON.stringify(notification));
}
},
});
useEffect(() => {
// Usesd to display notification when app is in foreground
const unsubscribe = messaging().onMessage(async (remoteMessage) => {
PushNotificationIos.addNotificationRequest({
id: remoteMessage.messageId,
body: remoteMessage.notification.body,
title: remoteMessage.notification.title,
userInfo: remoteMessage.data,
});
});
return unsubscribe;
}, []);
return null;
};
export default NotificationController;
Now, call <NotificationController /> in you Home screen or App initial routing file.
I agree with all the above solutions...
I just wanted to add that, if you don't have channel id the use
PushNotification.createChannel(
{
channelId: 'fcm_fallback_notification_channel', // (required)
channelName: 'My channel', // (required)
channelDescription: 'A channel to categorise your notifications', // (optional) default: undefined.
soundName: 'default', // (optional) See `soundName` parameter of `localNotification` function
importance: 4, // (optional) default: 4. Int value of the Android notification importance
vibrate: true, // (optional) default: true. Creates the default vibration patten if true.
},
created => console.log(`createChannel returned '${created}'`),
);
and be careful while using
const dat = {
channelId: 'fcm_fallback_notification_channel', // (required)
channelName: 'My channel',
//... You can use all the options from localNotifications
message: notification.body, // (required)
title: notification.title,
};
console.log(dat)
PushNotification.localNotification(dat);
In some case when title: undefined, or title: Object{}, same for message might be happening so console log every thing and put it inside localNotification fuction
Following #Kishan Bharda solution, I had to do something different for IOS foreground notifications (here, I have the code in index.js instead of a different file):
import { AppRegistry, Platform } from 'react-native';
import App from './App';
import { name as appName } from './app.json';
import PushNotificationIOS from "#react-native-community/push-notification-ios";
import PushNotification from "react-native-push-notification";
if (Platform.OS === 'ios') {
// Must be outside of any component LifeCycle (such as `componentDidMount`).
PushNotification.configure({
onNotification: function (notification) {
console.log("NOTIFICATION:", notification);
const { foreground, userInteraction, title, message } = notification;
if (foreground && (title || message) && !userInteraction) PushNotification.localNotification(notification);
notification.finish(PushNotificationIOS.FetchResult.NoData);
}
});
}
AppRegistry.registerComponent(appName, () => App);

How to Take Action / Move Screen When FCM Notification is Opened?

I am currently using FCM React Native Firebase, where I want to move the screen or take action when I open the fcm notification, like the image below :
react-native-fcm is now deprecated and no longer maintained. It is better to move to react-native-firebase.
Below shows example for react-native-firebase.
First you need to create a notification listener in your application. Mostly it is better to add it on top level root component.
You can passed relevant data on you notification payload data object. https://firebase.google.com/docs/cloud-messaging/concept-options
import firebase from 'react-native-firebase';
componentDidMount() {
this.createNotificationListeners();
}
componentWillUnmount() {
// Remove all the notification related listeners on unmounting the main dashboard
if (this.notificationOpenedListener) {
this.notificationOpenedListener();
}
}
/**
* Contains all the listeners related to the Firebase notification services.
*/
async createNotificationListeners() {
const handleNotification = notificationOpen => {
// Do what ever do you want, based on your notification payload
};
/*
* If app is in background, listen for when a notification is clicked / tapped / opened as follows:
* */
try {
this.notificationOpenedListener = firebase
.notifications()
.onNotificationOpened(notificationOpen => {
console.log(
'FirebaseDataReceiver remote notification clicked from background :',
notificationOpen,
);
handleNotification(notificationOpen);
});
} catch (e) {
console.log(
'Error while clicking the remote notification from background :',
e,
);
}
/*
* If app is closed, check if it was opened by a notification being clicked / tapped / opened as follows:
* */
try {
const notificationOpen = await firebase
.notifications()
.getInitialNotification();
if (notificationOpen) {
console.log(
'FirebaseDataReceiver remote notification clicked app start up :',
notificationOpen,
);
handleNotification(notificationOpen);
}
} catch (e) {
console.log(
'Error while clicking the app was initiated by a remote notification :',
e,
);
}
}

UI freezes when I login and navigate to new screen. The screen works once I refresh the app. Any idea why?

I've building a React Native app and using redux and have implemented the React-Navigation. I have a couple off issues I'm working through and below is one of them that might be able to tackle both problems.
My screen flow is supposed to look like this:
Login (via Facebook)
Dashboard (via automatic re-direct using React-Navigation)
While this flow works, I'm finding that once I land on the Dashboard, the screen is frozen. If I restart the app, the app does work as intended (i.e. it makes a firebase call to retrieve my credentials, and automatically redirects from the Login screen to the Dashboard). The Dashboard then accepts all touches and the UI works fine.
Any idea what might be going? I feel like the issue is with how I'm setting up my listeners. I've implemented my navigation into redux as well. I've pulled out the relevant code below, but you can find the full code in the github links as well.
.src/actions/actions.js
(github link: actions.js)
// User Stuff
export const watchUserData = () => (
(dispatch) => {
currentUserListener((user) => {
// if (user !== null) {
if (user) {
console.log('from action creator login: ' + user.displayName);
dispatch(loadUser(user));
dispatch(watchReminderData(user.uid)); //listener to pull reminder data
dispatch(watchContactData(user.uid)); //listener to pull contact data
dispatch(watchPermissions(user.uid)); //listener to pull notificationToken
} else {
console.log('from action creator: ' + user);
dispatch(removeUser(user));
dispatch(logOutUser(false));
dispatch(NavigationActions.navigate({ routeName: 'Login' }));
}
});
}
);
export const watchUserDataForLogin = () => (
(dispatch) => {
currentUserListener((user) => {
if (!_.isEmpty(user)) {
dispatch(loadUser(user));
dispatch(setLoggedInUser(true));
dispatch(NavigationActions.navigate({ routeName: 'Dashboard' }));
}
});
}
);
.src/screens/Login.js
github link: Login.js
componentDidMount = () => {
this.unsubscribeCurrentUserListener = currentUserListener((snapshot) => {
try {
this.props.watchUserDataForLogin();
} catch (e) {
this.setState({ error: e, });
}
});
};
componentWillUnmount = () => {
if (this.unsubscribeCurrentUserListener) {
this.unsubscribeCurrentUserListener();
}
};
.src/screens/Dashboard.js
github link: Dashboard.js
componentDidMount = () => {
// Listener that loads the user, reminders, contacts, and notification data
// this.unsubscribeCurrentUserListener = currentUserListener((snapshot) => {
// try {
// this.props.watchUserData();
// } catch (e) {
// this.setState({ error: e, });
// }
// });
this.unsubscribeCurrentUserListener = this.props.watchUserData();
};
Let me know if you need additional information. Was trying to keep it succinct, but can add more details. Thanks!
Okay, I figured out what was going on. I had set up the listener incorrectly and so I couldn't unsubscribe from it once I left the login screen. The code now looks like this:
src/screen/Login.js
componentDidMount = () => {
this.unsubscribeCurrentUserListener = this.props.watchUserDataForLogin();
};
componentWillUnmount = () => {
if (this.unsubscribeCurrentUserListener) {
this.unsubscribeCurrentUserListener();
}
};

How can I get my firebase listener to load data to my redux state in a React Native app so I can read the data within my ComponentDidMount function?

I am trying to load a notification token (notificationToken) that I've stored within Firebase to a React Native component.
Once the notificationToken is loaded to my redux state, I want to check for my device permissions to see if the notificationToken has expired within the function getExistingPermission() that I run in the componentDidMount().
If the token has expired, then I'll replace the token within Firebase with the new token. If it's the same, then nothing happens (which is intended functionality).
When I'm running my function getExistingPermission() to check if the token is up-to-date the Firebase listener that pulls the notificationToken does not load in time, and so it's always doing a write to the Firebase database with a 'new' token.
I'm pretty sure using async/await would solve for this, but have not been able to get it to work. Any idea how I can ensure that the notificationToken loads from firebase to my redux state first before I run any functions within my componentDidMount() function? Code below - thank you!
src/screens/Dashboard.js
Should I use a .then() or async/await operator to ensure the notificationToken loads prior to running it through the getExistingPermission() function?
import {
getExistingPermission
} from '../components/Notifications/NotificationFunctions';
componentDidMount = async () => {
// Listener that loads the user, reminders, contacts, and notification data
this.unsubscribeCurrentUserListener = currentUserListener((snapshot) => {
try {
this.props.watchUserData();
} catch (e) {
this.setState({ error: e, });
}
});
if (
!getExistingPermission(
this.props.notificationToken, //this doesn't load in time
this.props.user.uid)
) {
this.setState({ showNotificationsModal: true });
}
};
src/components/Notifications/NotificationFunctions.js
The problem is probably not here
export const getExistingPermission = async (
notificationToken,
uid,
) => {
const { status: existingStatus } = await Permissions.askAsync(
Permissions.NOTIFICATIONS
);
if (existingStatus !== 'granted') {
console.log('status not granted');
return false;
} else {
let token = await Notifications.getExpoPushTokenAsync();
/* compare to the firebase token; if it's the same, do nothing,
if it's different, replace */
if (token === notificationToken) {
console.log('existing token loaded');
return true;
} else {
console.log('token: ' + token);
console.log('notificationToken: ' + notificationToken);
console.log('token is not loading, re-writing token to firebase');
writeNotificationToken(uid, token);
return false;
}
}
};
src/actions/actions.js
// Permissions stuff
watchPermissions = (uid) => (
(dispatch) => {
getPermissions(uid + '/notificationToken', (snapshot) => {
try {
dispatch(loadNotificationToken(Object.values([snapshot.val()])[0]));
}
catch (error) {
dispatch(loadNotificationToken(''));
// I could call a modal here so this can be raised at any point of the flow
}
});
}
);
// User Stuff
export const watchUserData = () => (
(dispatch) => {
currentUserListener((user) => {
if (user !== null) {
console.log('from action creator: ' + user.displayName);
dispatch(loadUser(user));
dispatch(watchReminderData(user.uid)); //listener to pull reminder data
dispatch(watchContactData(user.uid)); //listener to pull contact data
dispatch(watchPermissions(user.uid)); //listener to pull notificationToken
} else {
console.log('from action creator: ' + user);
dispatch(removeUser(user));
dispatch(logOutUser(false));
dispatch(NavigationActions.navigate({ routeName: 'Login' }));
}
});
}
);
export const loadNotificationToken = (notificationToken) => (
{
type: 'LOAD_NOTIFICATION_TOKEN',
notificationToken,
}
);
Tony gave me the answer. Needed to move the permissions check to componentDidUpdate(). For those having a similar issue, the component looks like this:
src/screens/Dashboard.js
componentDidUpdate = (prevProps) => {
if (!prevProps.notificationToken && this.props.notificationToken) {
if (!getExistingPermission(
this.props.notificationToken,
this.props.user.uid
)) {
this.setState({ showNotificationsModal: true });
}
}
};
Take a look at redux subscribers for this: https://redux.js.org/api-reference/store#subscribe . I implement a subscriber to manage a small state machine like STATE1_DO_THIS, STATE2_THEN_DO_THAT and store that state in redux and use it to render your component. Only the subscriber should change those states. That gives you a nice way to handle tricky flows where you want to wait on action1 finishing before doing action2. Does this help?

Categories