send push notification with vue-native and expo - vue.js

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?

Related

Expo Push Notification Foreground not working

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!

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

how to redirect to a screen when app is opened from push notification in react native

I am working on a react native app with aws-amplify library https://aws-amplify.github.io/docs/js/push-notifications .When I click on push notification, my app should redirect to notification screen.I am not getting the navigation props when i open the app from notification , So I am using this to work without prop https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html but still I am getting this error "undefined is not an object (evaluating 't.dispatch')".I am using react-navigation in my app
import PushNotification from '#aws-amplify/pushnotification';
import NotificationList from './NotificationList';
import NavigationService from './NavigationService';
Analytics.configure(awsmobile);
PushNotification.configure(awsmobile);
if(Platform.OS === 'android'){
PushNotification.onNotification((notification) => {
// console.log('in app notification : Outside App', notification);
Auth.currentAuthenticatedUser({
bypassCache: false // Optional, By default is false. If set to true, this call will send a request to Cognito to get the latest user data
}).then(user => {
console.log('Current user : Outside App', user.attributes.sub)
const currentDate = new Date();
const todayDate = moment(currentDate).format("YYYY-MM-DD")
var todayTime = moment().format('HH:mm:ss')
const notificationDetails = {
userId: user.attributes.sub,
userTitle: notification.title,
userBody: notification.body,
userDate: todayDate,
userTime: todayTime,
};
console.log("notificationDetails outside", notificationDetails)
API.graphql(graphqlOperation(createUserNotification, { input: notificationDetails }))
.then(response => {
console.log(JSON.stringify(response, null, 2));
})
.catch(err => {
console.log('Error Saving Details...', err);
this.setState({ showActivityIndicator: false });
});
});
});
PushNotification.onNotificationOpened((notification) => {
const b = new Home();
console.log('onNotificationOpened 1');
b._handleNotificationOpen(notification)
});
}
class Home extends React.Component {
constructor(props) {
super(props);
global.ShowRecordVar = "12"
this._handleNotificationOpen =
this._handleNotificationOpen.bind(this);
this.state = {
apiResponse: 0,
loading: true,
}
}
_handleNotificationOpen = (notification) => {
if (notification["pinpoint.deeplink"]) {
NavigationService.navigate('NotificationList',{notification});
}
}
//Some More Codes
}

VueJS - VueX : displaying notification after async process

Extract of my Single File Component:
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
data () {
return {
firstname: this.$store.getters.user.firstName,
lastname: this.$store.getters.user.lastName,
}
},
methods: {
...mapActions([
'updateUserProfile'
]),
// Function called when user click on the "Save changes" btn
onSubmit () {
console.log('Component(Profile)::onSaveChanges() - called');
const userData = {
firstName: this.firstname,
lastName: this.lastname
}
this.updateUserProfile(userData);
}
}
}
</script>
In my VueX store:
I already manage a LOADING state which is used to display a loading spinner.
Now, i would like to display a notification widget programmatically by using toastr library: toastr.success("Your profile has been updated");
Where should I place this code ? I suppose that is not a good practice to put this code directly on the updateUserProfile function of the store, but more on the Single File Component where the call is made ?
/*
* Action used to fetch user data from backend
*/
updateUserProfile ({commit, state}, userData) {
if (!state.jwtToken) {
return
}
// Inform VueX that we are currently loading something. Loading spinner will be displayed.
commit('SET_IS_LOADING', true);
axiosBackend.put('/user/profile', userData, { headers: { Authorization: state.authString } } ).then(res => {
console.log('PUT /user/profile', res);
// Store user data in local storage
localStorage.setItem('user', JSON.stringify(res.data.data));
// Set user Data in VueX Auth store
commit('SET_USER_DATA', {
user: res.data.data
});
// Reset is Loading
commit('SET_IS_LOADING', false);
})
.catch(error => {
// Reset isLoading
commit('SET_IS_LOADING', false);
});
}
You can return a Promise from action
updateUserProfile ({commit, state}, userData) {
if (!state.jwtToken) {
return
}
// Inform VueX that we are currently loading something. Loading spinner will be displayed.
commit('SET_IS_LOADING', true);
return axiosBackend.put('/user/profile', userData, { headers: { Authorization: state.authString } } ).then(res => {
console.log('PUT /user/profile', res);
// Store user data in local storage
localStorage.setItem('user', JSON.stringify(res.data.data));
// Set user Data in VueX Auth store
commit('SET_USER_DATA', {
user: res.data.data
});
// Reset is Loading
commit('SET_IS_LOADING', false);
return res.data.data
})
.catch(error => {
// Reset isLoading
commit('SET_IS_LOADING', false);
throw error
});
}
and then in Vue component:
onSubmit () {
console.log('Component(Profile)::onSaveChanges() - called');
const userData = {
firstName: this.firstname,
lastName: this.lastname
}
this.updateUserProfile(userData)
.then(data => {
toastr.success("Your profile has been updated");
})
.catch(error => {
console.error(error)
})
}
You should probably return the promise from your action:
/*
* Action used to fetch user data from backend
*/
updateUserProfile ({commit, state}, userData) {
if (!state.jwtToken) {
throw new Error('unauthenticated')
}
// Inform VueX that we are currently loading something. Loading spinner will be displayed.
commit('SET_IS_LOADING', true);
return axiosBackend.put('/user/profile', userData, { headers: { Authorization: state.authString } } ).then(res => {
console.log('PUT /user/profile', res);
// Store user data in local storage
localStorage.setItem('user', JSON.stringify(res.data.data));
// Set user Data in VueX Auth store
commit('SET_USER_DATA', {
user: res.data.data
});
// Reset is Loading
commit('SET_IS_LOADING', false);
return res.data.data
})
.catch(error => {
// Reset isLoading
commit('SET_IS_LOADING', false);
throw error
});
}
And then use this promise in your component:
onSubmit () {
console.log('Component(Profile)::onSaveChanges() - called');
const userData = {
firstName: this.firstname,
lastName: this.lastname
}
this.updateUserProfile(userData).then(user => {
toastr.success("Your profile has been updated")
}).catch(error => {
toastr.error("Something bad happened")
})
}

Too many requests when controlling spinner show/hide from axois interceptors

I have an SPA written in Vue (Webpack) where I want to control the visibility of a spinner based on whether or not the app is currently handling an HTTP request or a response.
Following some tutorials, I came up with the event bus scheme and did this:
Created eventBus.js:
import Vue from 'vue';
export const eventBus = new Vue();
I'm setting my axios interceptors in the created() hook of App.vue. Here's what the necessary functions look like in that component:
data() {
return {
showLoader: false
};
},
created(){
this.setAxiosInterceptors();
// some code removed //
}
},
mounted() {
eventBus.$on('show-loader', () => {
this.showLoader = true;
});
eventBus.$on('hide-loader', () => {
this.showLoader = false;
});
},
methods: {
setAxiosInterceptors() {
var tokenCookieName = this.$store.getters.getCookieNames.apiToken;
var cookieDefaultValue = this.$store.getters.getCookieDefaultValue;
// token expired middleware
this.axios.interceptors.response.use(response => {
var data = response.data;
if(data.info.api_token) {
this.$cookie.set(tokenCookieName, data.info.api_token);
}
if(data.status == 'error' && data.info.login_failed) {
this.$cookie.set(tokenCookieName, cookieDefaultValue);
window.location = '/'; // not possible to use Vue router here
}
eventBus.$emit('hide-loader');
return response;
},
error => {
eventBus.$emit('hide-loader');
console.log('Response interception failed!');
return Promise.reject(error);
});
// attach API token middleware
this.axios.interceptors.request.use(config => {
var apiToken = this.$cookie.get(tokenCookieName);
if (!apiToken) {
apiToken = cookieDefaultValue;
}
config.headers.Authorization = 'Bearer ' + apiToken;
eventBus.$emit('show-loader');
return config;
},
error => {
eventBus.$emit('hide-loader');
console.log('Request interception failed!');
return Promise.reject(error);
}
);
}
}
Please ignore some of the code that isn't relevant to the problem, but I wanted to show how things are set up. Problem is, as soon as I visit my home page, the app keep making the startup GET requests over and over, until my server returns a 429 error.
Interestingly, in my eventBus.$on handlers, if I just do a console.log, this behavior doesn't appear (of course, the spinner doesn't work as well) but as soon as I change a variable or call a vuex action, this infinite reloading starts.
Any clue?
In the main.js file
Vue.prototype.$axios = axios.create(
{
headers:
{
'Content-Type': 'application/json',
},
baseURL: process.env.API_URL
}
);
Vue.prototype.$axios.interceptors.request.use(
config =>
{
eventBus.$emit('show_spin');
let token = getTokenID();
if(token && token.length) config.headers['Authorization'] = token;
return config;
},
error =>
{
eventBus.$emit('hide_spin');
if (error.status === 401) VueRouter.push('/login');
else throw error;
}
);
Vue.prototype.$axios.interceptors.response.use(
response =>
{
eventBus.$emit('hide_spin');
return response;
},
error =>
{
eventBus.$emit('hide_spin');
return new Promise(function(resolve,reject)
{
if (error.config && error.response && error.response.status === 401 && !error.config.__isRetry)
{
myVue.refreshToken(function()
{
error.config.__isRetry = true;
error.config.headers['Authorization'] = getTokenID();
myVue.$axios(error.config).then(resolve,reject);
},function(flag) // true = invalid session, false = something else
{
if(process.env.NODE_ENV === 'development') console.log('Could not refresh token');
if(getUserID()) myVue.showFailed('Could not refresh the Authorization Token');
reject(flag);
});
}
else throw error;
});
}
);
let myVue = new Vue(
{
el: '#app',
data: function()
{
return {
spin_visible: 0, // dynamically show/hide spinner
};
},
created: function()
{
eventBus.$on('show_spin', this.showSpin);
eventBus.$on('hide_spin', this.hideSpin);
},
methods:
{
showSpin: function()
{
this.spin_visible++;
},
hideSpin: function()
{
if(this.spin_visible>0) this.spin_visible--;
},
....
and then in App.vue
<template>
<router-view/>
<div class="spinner" v-show="$root.spin_visible">
<!-- define your spinner here -->
</div>
</template>