Voximplant push notifications - React Native - voximplant

I have successfully managed to get remote messages from Firebase console to send messages when my app is in the QUIT state, FOREGREOUND state and in BACKGROUND state. So all the Firebase setup is working PERFECTLY in my React Native app (Android).
I managed to get Firebase Push Notifications working very well, using the latest Firebase messaging library.
The Version 5 'react-native-firebase' is deprecated and no longer supported, we have to use Version 6 '#react-native-firebase/messaging' for messaging and 'react-native-push-notification' (for the channel stuff and notification). This is good for the future in all React Native work, as the old version is becoming obsolete and Firebase isn't supporting it.
The google-services.json works fine and the push certificate is also successfully uploaded in the Voximplant panel.
I can already make calls when both Voximplant users are on the app (i.e. app states are in the FOREGROUND).
The MAIN reason for needing the push, is to allow another Voximplant user to receive a call from the app QUIT and BACKGROUND state.
What seems to be missing?
Here's my code:
package.json
"#react-native-firebase/app": "^15.2.0",
"#react-native-firebase/messaging": "^15.2.0",
"react-native-voximplant": "^1.28.0",
index.js
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
import messaging from '#react-native-firebase/messaging';
import PushBackground from './manager/PushBackground';
//Set handler
messaging().setBackgroundMessageHandler(PushBackground);
// NOT USING (but still works with pure firebase incoming push notifications)
//messaging().setBackgroundMessageHandler(async remoteMessage => {
// console.log('Message handled in the background!', remoteMessage);
//});
AppRegistry.registerComponent(appName, () => App);
App.js
import React, { useState, useEffect } from "react";
import { requestUserPermission, NotificationListener, CancelNotifications } from "./manager/PushManager";
function App() {
useEffect(() => {
//Firebase messaging/notifications
requestUserPermission(); //gets "fcmtoken"
NotificationListener(); //sets up firebase listeners for (QUIT, FOREGROUND, BACKGROUND App states)
CancelNotifications(); //Cancels notifications after received
}, []);
return(...all UI stuff...)
export default App;
}
loginScreen.js
import React, { Component, useEffect, useState, useContext } from "react";
import { Voximplant } from 'react-native-voximplant';
import { APP_NAME, ACC_NAME } from "../../constant/constants";
import AsyncStorage from '#react-native-async-storage/async-storage';
const LoginScreen = ({ navigation }) => {
const voximplant = Voximplant.getInstance();
useEffect(() => {
const connect = async() => {
const status = await voximplant.getClientState();
if(status == Voximplant.ClientState.DISCONNECTED){
await voximplant.connect();
console.log("Status: Voximplant Client DISCONNECTED: " + status);
}
else if (status == Voximplant.ClientState.LOGGED_IN) {
console.log("Status: Voximplant Client LOGGED IN: " + status);
}
const signIn = async () => {
try{
const fqUsername = `${username}#${APP_NAME}.${ACC_NAME}.voximplant.com`;
console.log(fqUsername);
let authResult = await voximplant.login(fqUsername, password);
const loginTokens = authResult.tokens;
console.log("Log In Successful. Token " + JSON.stringify(loginTokens, null, 2));
//We have fcmtoken (firebase) from App.js loading up, so now register it with Voximplant cloud
const fcmToken = await AsyncStorage.getItem("fcmtoken");
//Register it with Voximplant
if(fcmToken != null){
voximplant.registerPushNotificationsToken(fcmToken);
}
//Set fqUsername for Voximplant
await AsyncStorage.setItem("fqUsernameKey", fqUsername);
//Set accessToken for Voximplant
await AsyncStorage.setItem('accessTokenKey', authResult.tokens.accessToken);
}
catch(e) {
console.log("signIn issue: "+ e);
Alert.alert("Already Logged In", `Error code: ${e.code}`);
}
}
return(...UI stuff...)
}
export default LoginScreen;
PushBackground.js
'use strict';
import AsyncStorage from '#react-native-async-storage/async-storage';
import { Voximplant } from 'react-native-voximplant';
export default async (message) => {
console.log('PushBackground android: notification: ' + JSON.stringify(message));
const username = await AsyncStorage.getItem("fqUsernameKey"); //THIS IIS SET IN LOGINSCREEN.JS
const accessToken = await AsyncStorage.getItem("fcmtoken"); //THIS IS SET IN PUSHMANAGER.JS
const client = Voximplant.getInstance();
await client.loginWithToken(username, accessToken);
await client.handlePushNotification(message.data);
return Promise.resolve();
};
PushManager.js
import AsyncStorage from '#react-native-async-storage/async-storage';
import messaging from "#react-native-firebase/messaging";
import PushNotification, {Importance} from 'react-native-push-notification';
async function getFCMToken() {
let fcmtoken = await AsyncStorage.getItem("fcmtoken");
console.log(fcmtoken, "old token")
if(!fcmtoken){
try{
const fcmtoken = await messaging().getToken();
if(fcmtoken) {
console.log(fcmtoken, "new token");
await AsyncStorage.setItem("fcmtoken", fcmtoken);
}
}
catch (error) {
console.log(error, "error in fcmtoken")
}
}
}
//Create Push Notification channel
async function createVoximplantChannel() {
PushNotification.createChannel( //Haven't tried with Voximplant
{
channelId: "voximplant_channel_id", // "channel-id", // (required)
channelName: "Incoming call channel", //"My channel", // (required)
channelDescription: "Incoming call received", //"A channel to categorise your notifications", // (optional) default: undefined.
playSound: true, // (optional) default: true
soundName: "default", // (optional) See `soundName` parameter of `localNotification` function
importance: 5, // (optional) default: 4. Int value of the Android notification importance. importance: 2 <- SILENT NOTIFICATION CHANNEL & importance: 5 <- POPUP NOTIFICATION CHANNEL
vibrate: false, // (optional) default: true. Creates the default vibration patten if true.
},
(created) => console.log(`createChannel returned '${created}'`) // (optional) callback returns whether the channel was created, false means it already existed.
);
}
export async function requestUserPermission() {
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (enabled) {
console.log('Authorization Status: ', authStatus);
getFCMToken();
createVoximplantChannel();
}
}
export const NotificationListener = () => {
//******** Notification from BACKGROUND state app ********
messaging().onNotificationOpenedApp((remoteMessage) => {
PushNotification.localNotification({
channelId: "voximplant_channel_id", //remoteMessage.notification.android.channelId, //Perhaps "voximplant_channel_id" ?
message: remoteMessage.notification.body,
title: remoteMessage.notification.title,
//bigPictureUrl: remoteMessage.notification.android.imageUrl,
//smallIcon: remoteMessage.notification.android.imageUrl,
vibrate: true,
data: remoteMessage.data,
})
});
//******** Notification from QUIT state app ********
messaging()
.getInitialNotification()
.then( (remoteMessage) => {
PushNotification.localNotification({
channelId: "voximplant_channel_id", //remoteMessage.notification.android.channelId, //Perhaps "voximplant_channel_id" ?
message: remoteMessage.notification.body,
title: remoteMessage.notification.title,
//bigPictureUrl: remoteMessage.notification.android.imageUrl,
//smallIcon: remoteMessage.notification.android.imageUrl,
vibrate: true,
data: remoteMessage.data,
})
});
//******** Notification from FOREGROUND state app ********
messaging().onMessage(async (remoteMessage) => {
PushNotification.localNotification({
channelId: "voximplant_channel_id", //remoteMessage.notification.android.channelId,
message: remoteMessage.notification.body,
title: remoteMessage.notification.title,
//bigPictureUrl: remoteMessage.notification.android.imageUrl,
//smallIcon: remoteMessage.notification.android.imageUrl,
vibrate: true,
data: remoteMessage.data,
})
});
}
//Cancel notifications (so hopefully no overlapping notificatons)
export const CancelNotifications = () => {
PushNotification.cancelAllLocalNotifications();
};

Related

Possible Unhandled Promise Rejection (id:0) TypeError: undefined is not an object (evaluating 'ImagePicker.Permissions.askAsync)

I am building an iOS app using React-Native and Expo. I have figured out how to access the user's Photo Library but am unable to request permissions from their device. It instead just accesses the camera roll without asking, which is obviously not allowed in iOS. Below is some of my code:
import React, {useState, useEffect, useRef} from 'react';
import * as ImagePicker from 'expo-image-picker';
import Permissions, {usePermissions} from 'expo-permissions';
import Constants from 'expo-constants'
//The useEffect function is what is ran when checking to see if the async function has been accepted or not
const Screen = ({navigation}) => {
const [ imagePicker1, setImagePicker1 ] = useState(null);
useEffect(() => {
const permission_get1 = async ()=> {
if (Platform.OS !== 'web'){
let { status } = await ImagePicker.Permissions.askAsync(Permissions.MEDIA_LIBRARY);
if (status !== 'granted'){
console.log('No permission given')
alert('Camera roll required for to upload photo from your library');
return;
}
/*if (status === 'granted'){
console.log('Permission granted')*/
let imagePicker1 = await ImagePicker.getMediaLibraryAsync()
setImagePicker1(imagePicker1)
console.log('It worked')
}
};
permission_get1();
}, []);
I ended up getting it to work using this: I believe the previous example was using a depreciated method.
import * as ImagePicker from 'expo-image-picker'
const pickImage = async ()=>{
const { granted } = await Permissions.askAsync(Permissions.CAMERA_ROLL)
if(granted){
console.log('access granted')
let data = await ImagePicker.launchImageLibraryAsync({
mediaTypes:ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect:[1,1],
quality:0.5,
})
console.log(data)
if (!data.cancelled){
setImage(data.uri);
}
}else{
Alert.alert('Permissions required to access camera roll.')
}
}

How to display Local Notification in Background

I am trying to set up a local notifications with certain time to pushed for user when the time arrives.
I want help in two steps :
1) How to let that notifications fired while the app is in Background
2) How to add a sound to notifications when fired
Also I want to know if the approach I am using is right ?
import React, {useEffect, useState} from 'react';
import { Text, View, Button, Vibration, Platform , Alert} from 'react-native';
import {Notifications} from 'expo';
import * as Permissions from 'expo-permissions';
import Constants from 'expo-constants';
const LocalNotificationsScreen = () => {
const [notification, setNotification] = useState({});
useEffect(() => {
askPermissions();
}, []);
let Times = [
{time: '3:16' , name : 'test1'}, {time : '0:7', name : 'test2'},
{time : '0:9', name : 'test3'}, {time : '2:8' , name : 'test4'},
{time : '3:37' , name : 'test5'} , {time : '3:39', name : 'test6'}
];
const askPermissions = async () => {
const { status: existingStatus } = await Permissions.getAsync(Permissions.NOTIFICATIONS);
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Permissions.askAsync(Permissions.NOTIFICATIONS);
finalStatus = status;
}
if (finalStatus !== 'granted') {
return false;
}
return true;
};
const localNotification = {
id : 1,
title : 'Testing',
body : 'The body ',
data: { name : 'This is the data'},
ios : {
sound : true,
_displayInForeground : true
},
android : {
name : 'Sound',
sound : true,
vibrate : [ 0, 250, 250, 250 ],
repeat : false
},
userText: 'Hi from Notfications'
}
const schedulingOptions = {
time : (new Date()).getTime() + 5000,
}
console.log((new Date()).getTime())
const currentTime = `${new Date().getHours()}:${new Date().getMinutes()}`;
console.log(currentTime);
const pushTime = () => {
return Times.map((t) => {
if (t.time === currentTime) {
let notificationId =
Notifications.scheduleLocalNotificationAsync(localNotification, schedulingOptions);
console.log(notificationId);
setTimeout(function () {
Notifications.cancelAllScheduledNotificationsAsync()
}, 8000);
}
})
}
pushTime();
Notifications.addListener(
notification => {
Vibration.vibrate();
console.log(notification);
setNotification(notification);
}
)
return (
<View>
<Text>LocalNotifications </Text>
</View>
);
}
export default LocalNotificationsScreen;
I have no idea with expo but I have used local notifications in my app by using this package..
Check local notifications section... I hope it will work with expo..
https://github.com/zo0r/react-native-push-notification
You can use notification schedule of react-native-firebase:
https://github.com/invertase/react-native-firebase-docs/blob/master/docs/notifications/scheduling-notifications.md
Install and setup react-native-firebase
create notification and schedule as below
Create a notificationListener to listen and trigger to show notification.
Setup a schedule notification firebase.notifications().scheduleNotification(notification, {
fireDate: date.getTime(),
}), notificationListener will be triggered when fireDate comes
.setSound(channel.sound) set sound for notification
import firebase from 'react-native-firebase';
// Build notification
const notificationListener = firebase
.notifications()
.onNotification(notification => {
const {title, body, data} = notification;
console.log('onNotification:', notification);
if (typeof onNotification == 'function')
onNotification(title, body, data);
const localNotification = new firebase.notifications.Notification({
sound: 'sound',
show_in_foreground: true,
show_in_background: true,
})
.setSound(channel.sound)
.setNotificationId(notification.notificationId)
.setTitle(title)
.setBody(body)
.setData(data)
.android.setChannelId('#string/default_notification_channel_id') // e.g. the id you chose above
.android.setSmallIcon('#mipmap/ic_launcher') // create this icon in Android Studio
.android.setColor('#000000') // you can set a color here
.android.setPriority(firebase.notifications.Android.Priority.High);
firebase
.notifications()
.displayNotification(localNotification)
.then(() => {
cancelNotification(notification.notificationId);
})
.catch(err => console.error(err));
});
// Schedule the notification for 1 minute in the future
const date = new Date();
date.setMinutes(date.getMinutes() + 1);
firebase.notifications().scheduleNotification(notification, {
fireDate: date.getTime(),
})

relay subscription onNext not triggered on react-native

I am a subscription setup but onNext is not getting triggered I am not sure why since this is my first time implementing subscription and docs was not much help with the issue.
Here are the code implementations:
import {
graphql,
requestSubscription
} from 'react-relay'
import environment from '../network';
const subscription = graphql`
subscription chatCreatedSubscription{
chatCreated{
id
initiate_time
update_time
support_id
category_id
email
name
}
}
`;
function chatCreated(callback) {
const variables = {};
requestSubscription(environment, {
subscription,
variables,
onNext: () => {
console.log("onNext");
callback()
},
updater: () => {
console.log("updater");
}
});
}
module.exports = chatCreated;
and here is my network for the subscription
import { Environment, Network, RecordSource, Store } from "relay-runtime";
import Expo from "expo";
import { SubscriptionClient } from "subscriptions-transport-ws";
import { WebSocketLink } from 'apollo-link-ws';
import { execute } from 'apollo-link';
import accessHelper from "../helper/accessToken";
const networkSubscriptions = async (operation, variables) => {
let token = await accessHelper();
if (token != null || token != undefined) {
const subscriptionClient = new SubscriptionClient("ws://localhost:3000/graphql",
{
reconnect: true,
connectionParams: {
Authorization: token,
},
});
execute(new WebSocketLink(subscriptionClient), {
query: operation.text,
variables,
});
}
}
const network = Network.create(fetchQuery, networkSubscriptions);
const store = new Store(new RecordSource());
const environment = new Environment({
network,
store
});
export default environment;
the subscription is called in a componentDidMount method on a component it executes but the onNext method inside the subscription is never triggered when new information is added to what the subscription is listening to.
so i figured out that my issue was the network js not being setup properly and the version of subscription-transport-ws. i added version 0.8.3 of the package and made the following changes to my network file:
const networkSubscriptions = async (config, variables, cacheConfig, observer) => {
const query = config.text;
let token = await accessHelper();
if (token != null || token != undefined) {
const subscriptionClient = new SubscriptionClient(`ws://${api}/graphql`,
{
reconnect: true,
connectionParams: {
Authorization: token,
},
});
subscriptionClient.subscribe({ query, variables }, (error, result) => {
observer.onNext({ data: result })
})
return {
dispose: subscriptionClient.unsubscribe
};
}
}
i hope this helps you if you get stuck with the same issue as mine.

Push notification in existing expo project

I have an existing app in expo create in react-native. In that app I have to add expo push notification functionality. I have created the NotificationController using below link:
https://docs.expo.io/versions/v32.0.0/guides/push-notifications
I now need to know where I have to place this controller. I am very new in mobile development and just need help about the correct placement of this controller so that we can show this to the customer.
Once you have created your push notification service , either this can be a function in a single file in side your services directory (create if it doesn't exists) , or a component.
Then import that function inside your main app.js and use it inside componentDidMount lifecycle function. This is just a sample code and I'm sure this can be improved further, However this is enough to get started.
push_notification.js
import {Permissions, Notifications} from 'expo';
import { ActivityIndicator, AsyncStorage} from 'react-native';
export default async () => {
try{
let previousToken = await AsyncStorage.getItem('pushToken');
if(previousToken){
return;
}else{
let {status} = await Permissions.askAsync(Permissions.NOTIFICATIONS);
console.log(status);
if(status !== 'granted'){
console.log("Don't like to receive push");
}
let token = await Notifications.getExpoPushTokenAsync();
return token;
}
} catch(err){
console.log(err);
}
};
App.js
import PushNotification from "../services/push_notifications";
import axios from 'axios';
async componentDidMount(){
let tokenFromStorage = await zlAsyncStorage.getItem('pushToken');
console.log('token from storage',tokenFromStorage);return;
let token = await PushNotification();
AsyncStorage.setItem('pushToken',token);
console.log("here");
//Save the token in couch db
await axios({
url:"http://192.168.8.148:5984/mycompany",
method: 'post',
timeout: 1000,
headers: {
'Accept-Encoding' : 'gzip, deflate',
'Content-Type':'application/json',
'Authorization':'Basic YTph'
},
data: {
user: "cheran",
tokenReceived : token,
},
auth: {
username: 'a',
password: 'a'
},
}).then(function(response){
//console.log(response);
});
}

Deep links in react-native-firebase notifications

I am using react-native-firebase with messaging to deliver notifications to my app with cloud functions, with admin.messaging().send(message), very similar to here: https://medium.com/the-modern-development-stack/react-native-push-notifications-with-firebase-cloud-functions-74b832d45386 .
I receive notifications when the app is in the background. Right now I am sending a text in the body of the notification, like 'a new location has been added to the map'. I want to be able to add some sort of deep link, so that when I swipe View on the notification (on iOS for example), it will take me to a specific screen inside the app. How do I pass data from the notification to the app?
I am using react-native-navigation in the app. I can only find code about deep links from inside the app (https://wix.github.io/react-native-navigation/#/deep-links?id=deep-links).
My solution was to use add what information I need in the data object of the notification message object:
in functions/index.js:
let message = {
notification: {
body: `new notification `
},
token: pushToken,
data: {
type: 'NEW_TRAINING',
title: locationTitle
}
};
and process as follows in the app for navigation:
this.notificationOpenedListener =
firebase.notifications().onNotificationOpened((notificationOpen: NotificationOpen) => {
if (notification.data.type === 'NEW_TRAINING') {
this.props.navigator.push({
screen: 'newtrainingscreen',
title: notification.data.title,
animated: true
});
}
I think you are fine with the "how firebase notification work"... cause of this, here is only an description of the Logic how you can Deeplinking into your App.
If you send a notification, add a data-field. Let's say your app has a Tab-Navigator and the sections "News","Service" and "Review".
In your Push-Notification - Datafield (let's name it "jumpToScreen" you define your value:
jumpToScreen = Service
I assume you still have the Handling to recieve Notifications from Firebase implemented.
So create an /lib/MessageHandler.js Class and put your business-logic inside.
import firebase from 'react-native-firebase';
/*
* Get a string from Firebase-Messages and return the Screen to jump to
*/
const getJumpPoint = (pointer) => {
switch (pointer) {
case 'News':
return 'NAV_NewsList'; // <= this are the names of your Screens
case 'Service':
return 'NAV_ServiceList';
case 'Review':
return 'NAV_ReviewDetail';
default: return false;
}
};
const MessageHandler = {
/**
* initPushNotification initialize Firebase Messaging
* #return fcmToken String
*/
initPushNotification: async () => {
try {
const notificationPermission = await firebase.messaging().hasPermission();
MessageHandler.setNotificationChannels();
if (notificationPermission) {
try {
return await MessageHandler.getNotificationToken();
} catch (error) {
console.log(`Error: failed to get Notification-Token \n ${error}`);
}
}
} catch (error) {
console.log(`Error while checking Notification-Permission\n ${error}`);
}
return false;
},
clearBadges: () => {
firebase.notifications().setBadge(0);
},
getNotificationToken: () => firebase.messaging().getToken(),
setNotificationChannels() {
try {
/* Notification-Channels is a must-have for Android >= 8 */
const channel = new firebase.notifications.Android.Channel(
'app-infos',
'App Infos',
firebase.notifications.Android.Importance.Max,
).setDescription('General Information');
firebase.notifications().android.createChannel(channel);
} catch (error) {
console.log('Error while creating Push_Notification-Channel');
}
},
requestPermission: () => {
try {
firebase.messaging().requestPermission();
firebase.analytics().logEvent('pushNotification_permission', { decision: 'denied' });
} catch (error) {
// User has rejected permissions
firebase.analytics().logEvent('pushNotification_permission', { decision: 'allowed' });
}
},
foregroundNotificationListener: (navigation) => {
// In-App Messages if App in Foreground
firebase.notifications().onNotification((notification) => {
MessageHandler.setNotificationChannels();
navigation.navigate(getJumpPoint(notification.data.screen));
});
},
backgroundNotificationListener: (navigation) => {
// In-App Messages if App in Background
firebase.notifications().onNotificationOpened((notificationOpen) => {
const { notification } = notificationOpen;
notification.android.setChannelId('app-infos');
if (notification.data.screen !== undefined) {
navigation.navigate(getJumpPoint(notification.data.screen));
}
});
},
appInitNotificationListener: () => {
// In-App Messages if App in Background
firebase.notifications().onNotificationOpend((notification) => {
notification.android.setChannelId('app-infos');
console.log('App-Init: Da kommt ne Message rein', notification);
firebase.notifications().displayNotification(notification);
});
},
};
export default MessageHandler;
In your index.js you can connect it like this:
import MessageHandler from './lib/MessageHandler';
export default class App extends Component {
state = {
loading: null,
connection: null,
settings: null,
};
async componentDidMount() {
const { navigation } = this.props;
await MessageHandler.initPushNotification();
this.notificationForegroundListener = MessageHandler.foregroundNotificationListener(navigation);
this.notificationBackgroundListener = MessageHandler.backgroundNotificationListener(navigation);
this.setState({ loading: false, data });
}
componentWillUnmount() {
this.notificationForegroundListener();
this.notificationBackgroundListener();
}
async componentDidMount() {
MessageHandler.requestPermission();
AppState.addEventListener('change', this.handleAppStateChange);
MessageHandler.clearBadges();
}
componentWillUnmount() {
AppState.removeEventListener('change', this.handleAppStateChange);
}
handleAppStateChange = (nextAppState) => {
if (nextAppState.match(/inactive|background/)) {
MessageHandler.clearBadges();
}
....
I hope this give you an Idea how to implement it for your needs.
I think you don't need to use deep links nor dynamic links but just use Firebase/Notifications properly. If I were you I would add the following logic in the componentDidMount method of your parent container:
async componentDidMount() {
// 1. Check notification permission
const notificationsEnabled = await firebase.messaging().hasPermission();
if (!notificationsEnabled) {
try {
await firebase.messaging().requestPermission(); // Request notification permission
// At this point the user has authorized the notifications
} catch (error) {
// The user has NOT authorized the notifications
}
}
// 2. Get the registration token for firebase notifications
const fcmToken = await firebase.messaging().getToken();
// Save the token
// 3. Listen for notifications. To do that, react-native-firebase offer you some methods:
firebase.messaging().onMessage(message => { /* */ })
firebase.notifications().onNotificationDisplayed(notification => { /* */ })
firebase.messaging().onNotification(notification => { /* */ })
firebase.messaging().onNotificationOpened(notification => {
/* For instance, you could use it and do the NAVIGATION at this point
this.props.navigation.navigate('SomeScreen');
// Note that you can send whatever you want in the *notification* object, so you can add to the notification the route name of the screen you want to navigate to.
*/
})
}
You can find the documentation here: https://rnfirebase.io/docs/v4.3.x/notifications/receiving-notifications