Expo Push Notification Foreground not working - react-native

Background Notifications: Working
Killed notificiations: Working
Token generation: Working
Permissions: Verified and working
What should I do to troubleshoot this properly? I have tried other methods of handling, and I believe I tried adding a notification property to app.json but nothing worked to my knowledge.
Thanks for your time!
// imports redacted, but contain expo notification, device etc
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: false,
}),
});
export default function App() {
const [expoPushToken, setExpoPushToken] = useState<string|undefined>('');
const [notification, setNotification] = useState<any>(false);
const notificationListener = useRef<any>();
const responseListener = useRef<any>();
useEffect(() => {
if(Device.isDevice){
registerForPushNotificationsAsync().then(token => setExpoPushToken(token));
// This listener is fired whenever a notification is received while the app is foregrounded
notificationListener.current = Notifications.addNotificationReceivedListener(notification => {
setNotification(notification);
});
// This listener is fired whenever a user taps on or interacts with a notification (works when app is foregrounded, backgrounded, or killed)
responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
console.log(response);
});
return () => {
Notifications.removeNotificationSubscription(notificationListener.current);
Notifications.removeNotificationSubscription(responseListener.current);
};
} else {
//
}
}, []);
return(view stuff)
}
// outside of functional component
async function registerForPushNotificationsAsync() {
let token;
if (Constants.isDevice) {
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') {
alert('Failed to get push token for push notification!');
return;
}
token = (await Notifications.getExpoPushTokenAsync({ experienceId: '#Expo-project-name' })).data; // commented project name for security
} else {
alert('Must use physical device for Push Notifications');
}
if (Platform.OS === 'android') {
Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
});
}
return token;
}

the fix to this solution is in the experienceId
Make sure your experienceID matches EXACTLY what your expo project name is.
I had mine where the #username/project-name 'project-name' portion was lowercase, but my project was actually named in CAPITAL letters, so #username/PROJECT-NAME
That's the fix!

Related

How to implement Agora SDK videocall to react-native project

I successfully implemented the agora SDK videocall module with virtual background to my react.js web app, but when I try to implement it to the react-native mobile version I keep getting erros I don't know how to solve. I'm a bit new to react-native so this migth be an easy fix but I can't find it.
Basically, after submitting a form with the uid, channel, role, and token (I have a token service) the videocall component is rendered.
These are my dependencies
"agora-access-token": "^2.0.4",
"agora-react-native-rtm": "^1.5.0",
"agora-extension-virtual-background": "^1.1.1",
"agora-rtc-sdk-ng": "^4.14.0",
"agora-rn-uikit": "^4.0.0",
"axios": "^0.27.2",
"react": "18.0.0",
"react-native": "0.69.4",
"react-native-agora": "^3.7.1",
"react-native-dotenv": "^3.3.1"
This is the main videocall component.
import React,{ useEffect } from "react";
import { useState } from "react";
import axios from "axios";
import { Call } from "./components/Call";
const VideoCallApp = ({ videoCallData }) => {
const [token, setToken] = useState("");
const [virtualBackgroundData, setVirtualBackgroundData] = useState({
type: "img",
// example
// type: 'img',
// value: ''
//
// type: 'blur',
// value: integer // blurring degree, low (1), medium (2), or high (3).
//
// type: 'color',
// value: string // color on hex or string
});
useEffect(() => {
const getToken = async () => {
const url = `${process.env.REACT_APP_AGORA_TOKEN_SERVICE}/rtc/${videoCallData.channel}/${videoCallData.role}/uid/${videoCallData.uid}`;
try {
const response = await axios.get(url);
const token = response.data.rtcToken;
setToken(token);
} catch (err) {
alert(err);
}
};
getToken();
}, []);
return (
token && (
<Call
rtcProps={{
appId: process.env.REACT_APP_AGORA_APP_ID,
channel: videoCallData.channel,
token: token,
uid: videoCallData.uid,
}}
virtualBackground={virtualBackgroundData}
/>
)
);
};
export default VideoCallApp;
Which renders the Call component, it has more functionality for the virtual background.
import { useEffect, useState } from 'react'
import AgoraRTC from 'agora-rtc-sdk-ng';
import VirtualBackgroundExtension from 'agora-extension-virtual-background';
import { LocalVideo } from './LocalVideo';
import { RemoteVideo } from './RemoteVideo';
import { VideoControllers } from './VideoButtons';
import { View } from 'react-native';
const client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
const extension = new VirtualBackgroundExtension();
AgoraRTC.registerExtensions([extension]);
export const Call = ({ rtcProps = {}, virtualBackground = {} }) => {
const [localTracks, setLocalTracks] = useState({
audioTrack: null,
videoTrack: null
});
const [processor, setProcessor] = useState(null);
useEffect(() => {
if (processor) {
try {
const initProcessor = async () => {
// Initialize the extension and pass in the URL of the Wasm file
await processor.init(process.env.PUBLIC_URL + "/assets/wasms");
// Inject the extension into the video processing pipeline in the SDK
localTracks.videoTrack.pipe(processor).pipe(localTracks.videoTrack.processorDestination);
playVirtualBackground();
}
initProcessor()
} catch (e) {
console.log("Fail to load WASM resource!"); return null;
}
}
}, [processor]);
useEffect(() => {
if (localTracks.videoTrack && processor) {
setProcessor(null);
}
}, [localTracks]);
const playVirtualBackground = async () => {
try {
switch (virtualBackground.type) {
case 'color':
processor.setOptions({ type: 'color', color: virtualBackground.value });
break;
case 'blur':
processor.setOptions({ type: 'blur', blurDegree: Number(virtualBackground.value) });
break;
case 'img':
const imgElement = document.createElement('img');
imgElement.onload = async () => {
try {
processor.setOptions({ type: 'img', source: imgElement });
await processor.enable();
} catch (error) {
console.log(error)
}
}
imgElement.src = process.env.PUBLIC_URL + '/assets/backgrounds/background-7.jpg';
imgElement.crossOrigin = "anonymous";
break;
default:
break;
}
await processor.enable();
} catch (error) {
console.log(error)
}
}
const join = async () => {
await client.join(rtcProps.appId, rtcProps.channel, rtcProps.token, Number(rtcProps.uid));
}
const startVideo = () => {
AgoraRTC.createCameraVideoTrack()
.then(videoTrack => {
setLocalTracks(tracks => ({
...tracks,
videoTrack
}));
client.publish(videoTrack);
videoTrack.play('local');
})
}
const startAudio = () => {
AgoraRTC.createMicrophoneAudioTrack()
.then(audioTrack => {
setLocalTracks(tracks => ({
...tracks,
audioTrack
}));
client.publish(audioTrack);
});
}
const stopVideo = () => {
localTracks.videoTrack.close();
localTracks.videoTrack.stop();
client.unpublish(localTracks.videoTrack);
}
const stopAudio = () => {
localTracks.audioTrack.close();
localTracks.audioTrack.stop();
client.unpublish(localTracks.audioTrack);
}
const leaveVideoCall = () => {
stopVideo();
stopAudio();
client.leave();
}
async function startOneToOneVideoCall() {
join()
.then(() => {
startVideo();
startAudio();
client.on('user-published', async (user, mediaType) => {
if (client._users.length > 1) {
client.leave();
alert('Please Wait Room is Full');
return;
}
await client.subscribe(user, mediaType);
if (mediaType === 'video') {
const remoteVideoTrack = user.videoTrack;
remoteVideoTrack.play('remote');
}
if (mediaType === 'audio') {
user.audioTrack.play();
}
});
});
}
// Initialization
function setProcessorInstance() {
if (!processor && localTracks.videoTrack) {
// Create a VirtualBackgroundProcessor instance
setProcessor(extension.createProcessor());
}
}
async function setBackground() {
if (localTracks.videoTrack) {
setProcessorInstance()
}
}
useEffect(() => {
startOneToOneVideoCall();
}, []);
return (
<View >
<View>
<LocalVideo />
<RemoteVideo />
<VideoControllers
actions={{
startAudio,
stopAudio,
startVideo,
stopVideo,
leaveVideoCall,
startOneToOneVideoCall,
setBackground
}}
/>
</View>
</View>
)
}
The local an remote video component are emty Views where the videos are displayed and the VideoControllers are Buttons that manage the videocall.
When I run the app the form works fine but as soon as I subbmit it the app crashes with these errors.
WARN `new NativeEventEmitter()` was called with a non-null argument without the required `addListener` method.
WARN `new NativeEventEmitter()` was called with a non-null argument without the required `removeListeners` method.
LOG Running "videocall" with {"rootTag":1}
ERROR TypeError: window.addEventListener is not a function. (In 'window.addEventListener("online", function () {
_this32.networkState = EB.ONLINE;
})', 'window.addEventListener' is undefined)
VideoCallApp
Form#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.videocall&modulesOnly=false&runModule=true:121056:41
RCTView
View
RCTView
View
AppContainer#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.videocall&modulesOnly=false&runModule=true:78626:36
videocall(RootComponent)
ERROR TypeError: undefined is not an object (evaluating '_$$_REQUIRE(_dependencyMap[5], "./components/Call").Call')
Something is happening at the Call component and I think it migth be the DOM manipulation for the videos but I can't find an example of a react-native project with agora SDK.
I don't want to use the UIkit because, eventhough it works, I can't use the virtual background which I need for this project. Can anyone help me?

Firestore keep loading old changes

I'm trying to create a firestore listener, which handles changes on my collection. After some research, I implement the feature as below.
useEffect(() => {
const firebaseApp = getFirebaseApp();
const db = firestore(firebaseApp);
const handleSnapshotChanges = ( snapshot: FirebaseFirestoreTypes.QuerySnapshot<FirebaseFirestoreTypes.DocumentData> ) => {
const changes = snapshot.docChanges();
changes.forEach((change) => {
if (change.type === "added") {
console.log(change.doc);
console.log(change.type);
}
if (change.type === "modified") {
console.log("Doc modified");
}
if (change.type === "removed") {
console.log("Remove doc");
}
});
};
const query = db.collection("history");
const unsubscribe = query.onSnapshot(handleSnapshotChanges, (err) =>
console.log(err)
);
return () => {
unsubscribe();
};
}, []);
If I doing so, every time I enter the screen where I put the above useEffect, firestore keeps loading all documents in the collection and marks them as added. How can I implement this function properly.

react native unsubscribe event listener

I have a react native component with two event listeners for linking and for dynamicLinks, how do I unsubscribe for both using hooks?
useEffect(() => {
// Update the document title using the browser API
if (Platform.OS === "ios") {
SecurityScreen.enabled(true);
}
// global.perra = "a";
usingAlternativeAPI();
Linking.addEventListener("url", deepLinkHandler);
const unsubscribe = dynamicLinks().onLink(handleDynamicLink);
// When the component is unmounted, remove the listener
return () => unsubscribe();
}, []);
Linking lib has a removeEventListener() function you can call with passing the url event type and the handler. This code should work.
useEffect(() => {
// useEffect code here
return function cleanup() {
unsubscribe();
Linking.removeEventListener("url", deepLinkHandler);
};
}, []);
Have you tried this before?
useEffect(() => {
// Update the document title using the browser API
if (Platform.OS === "ios") {
SecurityScreen.enabled(true);
}
// global.perra = "a";
usingAlternativeAPI();
const un = Linking.addEventListener("url", deepLinkHandler);
const unsubscribe = dynamicLinks().onLink(handleDynamicLink);
// When the component is unmounted, remove the listener
return () => {
unsubscribe();
un()
}
}, []);
At the moment the documentation points to do this way,
useEffect(() => {
const unsub = Linking.addEventListener("url", ({ url: _url }) => {
setUrl(_url);
});
return unsub.remove();
}, []);

Managing push notifications

i'm implementing push notifications in my app and i've the following proccess:
Device receive the notification > the user tap on notification > navigate to specific screen.
Searching in the web, i didn't find anything about it.
So, i've tried to implement a listener on receive the notification, but no success.
const _handleReceivedNotification = (notification: Notification) => {
const { data } = notification.request.content;
const { request } = notification;
console.log(data, request); // notification.request.content.data object has '{ screen: "Supply/Order/ApproveOrderDetail/712177" }'
try {
props.navigation.navigate("Supply", {
screen: "Order",
param: { screen: "ApproveOrderDetail", param: { id: data.id } },
});
} catch (error) {
console.error(error);
}
};
useEffect(() => {
registerForPushNotificationsAsync();
const subscription = Notifications.addNotificationReceivedListener(
_handleReceivedNotification
);
return () => {
subscription.remove();
console.log("subscribed");
};
}, []);
Has anyone implemented this feature?
I recommend using react-native-firebase libraries in your application. This way, you can take the actions you want when your notification is triggered with the code snippet below.
import firebase from '#react-native-firebase/app'
import '#react-native-firebase/messaging'
firebase.messaging().onNotificationOpenedApp((remoteMessage) => {
if (remoteMessage) {
// console.log('onNotificationOpenedApp ', remoteMessage)
}
})
If you use Expo, in Expo Doc u have an overview on how to use expo push (with a generate token by expo, u push one time with json like that :
{
"to": "ExponentPushToken[YOUR_EXPO_PUSH_TOKEN]",
"badge": 1,
"body": "You've got mail"
},
),
and it sending directly on IOS(apns) or Android(Firebase)
Outside your class :
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: false,
shouldSetBadge: false,
}),
});
In your init function (componentDidMount), set notification:
import * as Permissions from "expo-permissions";
import * as Notifications from "expo-notifications";
Notifications.addNotificationReceivedListener(this._handleNotification);
Notifications.addNotificationResponseReceivedListener(this._handleNotificationResponse);
//Push Notification init
await this.registerForPushNotificationsAsync();
registerForPushNotificationAsync()
async registerForPushNotificationsAsync() {
if (Constants.isDevice) {
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') {
alert('Failed to get push token for push notification!');
return;
}
let tokenPush = this.setAndFormatNotificationPushOnBdd();
this.setState({ expoPushToken: tokenPush });
} else {
alert('Must use physical device for Push Notifications');
}
if (Platform.OS === 'android') {
Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
});
}
};
Other functions :
_handleNotification = notification => {
this.setState({ notification: notification });
};
_handleNotificationResponse = response => {
console.log(response);
};
I just show you my Bdd save :
async setAndFormatNotificationPushOnBdd(){
try {
const tokenPush = await Notifications.getExpoPushTokenAsync();
let token = await AsyncStorage.getItem('token');
//On test le token et le régénère si besoin avec cette fonction
let validToken = await refreshToken(token);
//Si le retour n'est pas valide, on déconnecte l'utilisateur
if (!validToken) this.props.navigation.navigate('Disconnect');
let userInfo = await AsyncStorage.getItem('userInfo');
let userSession = await AsyncStorage.getItem('userSession');
let parsedUserSession = JSON.parse(userSession);
let tokenFormated = tokenPush.data;
await setNotificationPushId(parsedUserSession.user.n, tokenFormated, token,parsedUserSession)
return tokenFormated;
}catch (e) {
}
}

send push notification with vue-native and expo

I'm new to vue-native and vuejs and want to make an application with them. I want to add push notification to the app. I use this tutorial for add push notification and test it but I get this Error
Error in created hook: "TypeError:
this.registerForPushNotificationsAsync is not a function. (In
'this.registerForPushNotificationsAsync()',
'this.registerForPushNotificationsAsync' is undefined)"
I add this block of code in my project like this in app.vue file
<template>
<view class="container">
<text class="text-color-primary">{{JSON.stringify(notification.data)}}</text>
</view>
</template>
<script>
export default {
data: function() {
return {
notification: {}
};
},
created: function() {
this.registerForPushNotificationsAsync();
this._notificationSubscription = Notifications.addListener(
this._handleNotification
);
},
methods:{
_handleNotification: function(notification) {
this.notification = notification;
},
registerForPushNotifications: async function() {
const { status: existingStatus } = await Permissions.getAsync(
Permissions.NOTIFICATIONS
);
let finalStatus = existingStatus;
// only ask if permissions have not already been determined, because
// iOS won't necessarily prompt the user a second time.
if (existingStatus !== "granted") {
// Android remote notification permissions are granted during the app
// install, so this will only ask on iOS
const { status } = await Permissions.askAsync(
Permissions.NOTIFICATIONS
);
finalStatus = status;
}
// Stop here if the user did not grant permissions
if (finalStatus !== "granted") {
return;
}
// Get the token that uniquely identifies this device
Notifications.getExpoPushTokenAsync().then(token => {
console.log(token);
});
}
}
};
</script>
where i go wrong?