React Native check if app notification enabled on Android - android

User is able to turn off app notification on Android. Is there a way to check if the user did so?
The React Native doc did not mention Notification permission as it's not a permission that requires prompting the user.
Android docs did mention the ability to access notification policy.

I have used this one in previous experiences with good results:
https://github.com/jigaryadav/react-native-check-notification-enable
Basically what I ended up implementing as helpers were this two functions:
export const getNotificationStatus = async () => {
if (Platform.OS === 'ios') {
const status = await Permissions.check(PermissionRequested.NOTIFICATION)
if (status === PermissionsStatus.AUTHORIZED) {
return true
} else {
return false
}
} else {
// This is where I am using the library
const status = await NotificationManager.areNotificationsEnabled()
return status
}
}
export const openNotificationsSettings = () => {
if (Platform.OS === 'ios') {
Linking.openURL('app-settings:')
} else {
AndroidOpenSettings.appNotificationSettings()
}
}
Just to explain a bit the whole snippet:
I am also using these libraries in conjunction to achieve all: Check if they are enabled and navigate to the settings page to revoke if needed
import AndroidOpenSettings from 'react-native-android-open-settings'
import NotificationManager from 'react-native-check-notification-enable'
import Permissions from 'react-native-permissions'

I have found one library called React-native-permission-settings
It is available for android only so if you need iOS support also i think you need to modify it.
You can follow installation guide from here
And implement it in your code
import NotificationSettings from 'react-native-permission-settings';
...
NotificationSettings.areNotificationsEnabled((isEnabled: boolean) => {
console.log(`Notifications are enabled: ${isEnabled}`);
});

Related

firebase notifications stops working after a while (one day or a few days)

I am very frustrated with this problem:(
I am developing an app for android and ios (using capacitor 3) and I am sending notifications to the app via firebase notifications. (capacitor packages: #capacitor-community/fcm and #capacitor/push-notifications).
It works for a while and after one day or a few days that the app is running in background or foreground (and not killed) it stops from working and the app doesn't get notifications(This has happened to me in android device.).
I am sending notifications using topics and i also tried to send the notification through firebase console, but it didn't work.
I am not sure if this means that the registration token has expired because I would think that the capacitor packages are suppose to handle it since they are not talking about this problem.
I did everything from the documentation of capacitor push notifications.
When I watch the logs I can see the next error: Failed to sync topics. Won't retry sync. INVALID_PARAMETERS.
My code in javascript:
import '#capacitor/core';
import { ActionPerformed, PushNotificationSchema, PushNotifications } from '#capacitor/push-notifications'
import { FCM } from '#capacitor-community/fcm';
import { getMessaging, getToken as firebaseGetToken, onMessage, deleteToken, isSupported } from "firebase/messaging";
import { myAxios } from './generic-functions/my-axios';
const platform = window.Capacitor && window.Capacitor.platform;
const topicIos = `${process.env.REACT_APP_TOPIC}_ios`;
const topicAnd = `${process.env.REACT_APP_TOPIC}_and`;
function isCapacitor(): boolean {
//check if we are in a capacitor platform
return window.Capacitor && (window.Capacitor.platform === "android" || window.Capacitor.platform === "ios")
}
export async function InitFCM(destination: string) {
if (!isCapacitor()) {
const isNtfSupported = await isSupported()
if (!isNtfSupported) return
// web notifications
Notification.requestPermission().then(function (permission) {
if (permission === 'granted') {
subscribeTo(destination);
} else {
// Show some error
}
});
const messaging = getMessaging();
onMessage(messaging, (payload) => {
let notification = payload.data;
const notificationOptions: NotificationOptions = {
badge: notification?.largeIco,
body: notification?.body,
icon: notification?.largeIcon
};
const title = notification?.title || "";
// show notification
navigator.serviceWorker
.getRegistrations()
.then((registration) => {
if (notification?.sound) {
const audio = new Audio(`/notifications/${notification?.sound}`)
audio.play()
}
registration[0].showNotification(title, notificationOptions);
});
})
return
}
try {
console.log('Initializing Push Notifications');
// 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.requestPermissions().then(result => {
if (result.receive === 'granted') {
// Register with Apple / Google to receive push via APNS/FCM
// PushNotifications.register();
subscribeTo(destination);
} else {
// Show some error
}
});
// Some issue with our setup and push will not work
PushNotifications.addListener('registrationError',
(error: any) => {
console.log('Error on registration: ' + JSON.stringify(error));
}
);
// Show us the notification payload if the app is open on our device
PushNotifications.addListener('pushNotificationReceived',
(notification: PushNotificationSchema) => {
console.log('Push received: ' + JSON.stringify(notification));
}
);
// Method called when tapping on a notification
PushNotifications.addListener('pushNotificationActionPerformed',
(notification: ActionPerformed) => {
console.log('Push action performed: ' + JSON.stringify(notification));
}
);
} catch (e) {
console.log('err in push notifications: ', e);
}
}
async function subscribeTo(destination: string) {
if (!isCapacitor()) {
//subscribe to web topic
const messaging = getMessaging();
firebaseGetToken(messaging, { vapidKey: process.env.REACT_APP_FIREBASE_VAPID_KEY }).then(
async (token) => {
if (token) {
await myAxios.post("/api/notifications/subscribe-to-topic", { token, destination });
}
}).catch((err) => {
console.log('An error occurred while retrieving token. ', err);
});
return
}
try {
await PushNotifications.register();
if (platform === "ios") {
//subscribe to ios topic
const resIos = await FCM.subscribeTo({ topic: `${topicIos}_${destination}` });
console.log(`subscribed to ios Topic ${JSON.stringify(resIos)}`);
}
if (platform === "android") {
//subscribe to android topic
const resAnd = await FCM.subscribeTo({ topic: `${topicAnd}_${destination}` });
console.log(`subscribed to android Topic ${JSON.stringify(resAnd)}`);
}
} catch (error) {
console.log(JSON.stringify(error));
}
}
export async function getToken() {
try {
/* const result = */ await FCM.getToken();
// console.log("TOKEN", result.token);
} catch (error) {
console.log(error);
}
}
export async function unsubscribeFrom(destination?: string) {
if (!isCapacitor()) {
const isNtfSupported = await isSupported()
if (!isNtfSupported || !destination) return
const messaging = getMessaging();
//unsubscribe from web topic
firebaseGetToken(messaging, { vapidKey: process.env.REACT_APP_FIREBASE_VAPID_KEY }).then(
async (token) => {
if (token) {
await myAxios.post("/api/notifications/unsubscribe-from-topic", { token, destination });
}
}).catch((err) => {
console.log('An error occurred while retrieving token. ', err);
});
return
}
try {
await PushNotifications.removeAllListeners();
if (destination) {
if (platform === "ios") {
//unsubscribe from ios topic
const resIos = await FCM.unsubscribeFrom({ topic: `${topicIos}_${destination}` });
console.log(`unsubscribed from ios topic ${resIos}`);
}
if (platform === "android") {
//unsubscribe from android topic
const resAndroid = await FCM.unsubscribeFrom({ topic: `${topicAnd}_${destination}` });
console.log(`unsubscribed from android topic ${topicAnd}_${destination}: ${resAndroid.message}`);
}
}
} catch (error) {
console.log(error)
}
if (platform === 'android') {
await FCM.deleteInstance();
}
}
Thank you all in advanced!
This is a common issue since Android 7.0. The problem occurs because you make use of data messages. This part of your code onMessage(messaging, (payload) => { tells me that you rely on that. This means that when a message is received, your apps code will handle the delivery even when in the background. It will create a notification to show it on the device and play a sound for example.
Power Management taken too far
Several device manufacturers have improved their power management too far. This results in the following problem: After a few days of inactivity, an app is completely killed by the Android OS. This means that the app is not able to handle incoming messages in the background anymore. Vendors have gone too far. But you can't do anything about that.
What to do?
To solve the problem, you should rely on notification messages. These are messages that are directly delivered to the Android OS, instead of your app. This means that messages do not need background handling of your app. On the server (sending) side it means you have to modify your current message and add notification info to the message that is sent.
The drawback
The drawback of notification messages is that you can't lay your hands on the data that is part of the notification. If you previously filled your app with data from each notification, with notification messages, you get the data only when your app is in the foreground or the notification is clicked. To get all data within your app, you need a server API solution or something else.
To overcome this you can add a NotificationListener to your app. I am not sure how to do this in Capacitor. A native example can be found here: https://github.com/Chagall/notification-listener-service-example. The NotificationListener can listen for notifications delivered to the Android device also in the background. With this solution you can be sure notifications are always delivered and the data is delivered in the background. But maybe, I don't know, this listener is killed too by power management. When you use the NotificationListener, you need a special permission, that must be set via device settings (see the mentioned example).
Conclusion
Change from data messages to notification messages. Provide a different way to get the data of your messages in your app. You can use the NotificationListener but I don't know if that is reliable. The most obvious solution is to introduce a server side API that provides the data to your app. In the new situation the notifications are reliable delivered to the app.

Is there a native solution to pick files in NativeScript Angular?

I am trying to implement a feature to let the user upload a file in my NativeScript Angular Project. NativeScript does not seem to have a native implementation of a file picker and there are limited plugins available that can do the job. Plus they have their own set of problems. The closest I have come to a workable solution is using the nativescript-mediafilepicker and that opens a blank page like the one below instead of the file explorer.
I exactly followed the documentation and can't figure out why it's not working. Here is the service I wrote:
payload.service.ts
import { Injectable } from '#angular/core';
import { Mediafilepicker, ImagePickerOptions, VideoPickerOptions, AudioPickerOptions,
FilePickerOptions } from 'nativescript-mediafilepicker';
#Injectable({
providedIn: 'root'
})
export class PayloadService {
constructor() { }
pickFile(){
console.log('Pick File Payload Service requested');
const extensions = ['pdf'];
let options: FilePickerOptions = {
android: {
extensions: extensions,
maxNumberFiles: 1
},
ios: {
extensions: extensions,
multipleSelection: false
}
};
let mediafilepicker = new Mediafilepicker();
mediafilepicker.openFilePicker(options);
mediafilepicker.on("getFiles", function (res) {
let results = res.object.get('results');
console.dir('File Pick Success: ',results);
});
mediafilepicker.on("error", function (res) {
let msg = res.object.get('msg');
console.log('File Pick Error: ',msg);
});
mediafilepicker.on("cancel", function (res) {
let msg = res.object.get('msg');
console.log('File Pick Cancel: ',msg);
});
}
}
Can someone help me fix this or rather provide me with a native implementation? I don't need much customization options and user will only upload one file at a time.

Appstate keep on getting change in React native in Android

I am working on React native project and there I am taking location permissions. Also I have to track location permissions always like if user has given permission access after install the application and then after sometime user goes to the app settings in device settings and disable/revoked the permissions. Again once app comes from background to foreground, I have to check permission based on that, Needs to show the messages.
So that, I am using Appstate. But, In Android strangely, After installed the application, If user denied the permission with "Dont show again" checkbox, Then Appstate getting keep on changing with background and active always.
It is keep on loop.
componentDidMount = async () => {
AppState.addEventListener('change', this.handleAppStateChange);
};
componentWillUnmount() {
AppState.removeEventListener('change', this.handleAppStateChange);
Geolocation.clearWatch(this.watchID);
}
handleAppStateChange = async nextAppState => {
const {appState} = this.state;
console.log('nextAppState -->', nextAppState);
console.log('appState -->', appState);
if (appState === 'active') {
// do this
this.showLoader();
await this.requestAndroidLocationPermission();
} else if (appState === 'background') {
// do that
} else if (appState === 'inactive') {
// do that other thing
}
this.setState({appState: nextAppState});
};
requestAndroidLocationPermission = async () => {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{},
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
this.getLatitudeLongitude();
} else if (granted === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) {
this.hideLoader();
this.setState({
errorMessage: 'Location permission is denied',
isLoading: false,
});
} else {
this.hideLoader();
this.requestAndroidLocationPermission();
}
} catch (err) {
console.warn(err);
}
};
It is keep on printing (loop) after denied permission with Don't show again
appState --> active
nextAppState --> background
appState --> active
nextAppState --> background
appState --> active
nextAppState --> background
appState --> active
It goes on and never stop.
How to handle this? Any suggestions?
I had the same problem. Do not use AppState. Is faulty.
the problem lies within RN's definition of "background". react-native uses android's activity (the holder of the UI thread and where your UI lives) onPause callback as the trigger for sending the "background" signal. But, onPause is called everytime SOMETHING comes in front of your activity's view hierachy, like Dialogs (like the permission box), other activities (like a file picker), etc; for android react-native, "background" means "shadowed by a foreign UI element/android task" rather than "paused and sent to background to do something else", thus causing the loops you see. The shortest solution is to override onPause in your ReactActivity, and add control conditions to make sure super.onPause is only called when you are actually going to background, like checking your task stack, or if the permission dialog is being called, so you avoid this kind of loop/faulty call. A second option would be to provide your own app lifecycle event instead, with clear triggering conditions.
today I had a similar problem.
I could solve it using "focus" in android and "change" in ios.
I have a custom hook like this:
import { useEffect } from 'react';
import { AppState, Platform } from 'react-native';
const focusEvent = Platform.OS === 'ios' ? 'focus' : 'change';
const useLocationListener = () => {
useEffect(() => {
AppState.addEventListener(focusEvent, handleAppStateChange);
getLocationAsync();
return () => {
AppState.removeEventListener(focusEvent, handleAppStateChange);
};
}, []);
const handleAppStateChange = (nextAppState: string) => {
if (nextAppState === 'active') {
getLocationAsync();
}
};
const getLocationAsync = async () => {
const { canAskAgain, status } = await Permissions.getAsync(
Permissions.LOCATION
);
if (canAskAgain) {
const response = await Permissions.askAsync(Permissions.LOCATION);
// handle location
}
// handle location with "status"
};
};
export default useLocationListener;
You can use a flag that check whether app should handle background or it's just a permission call.
const shouldHandleBackground = useRef(true)
const handler = (state) => {
if (state === 'active' && shouldHandleBackground.current) {
doStuff()
}
}
// when calling for permisson make the flag false
shouldHandleBackground.current = false
await Android.permission.LocationPermission()
shouldHandleBackground.current = true
and after permission request you can make flag true

React-native-firebase notification in background (Android)

I am using the react-native-firebase library and I am brainstorming to customize my notifications when they are in the background, I tried to follow the documentation but to no avail, are there native code .java to handle notifications?
To customize the background notifications you can add in the index of your application:
import { AppRegistry } from 'react-native'
import App from './App'
import { backgroundMessageNotificationHandler } from 'src/common/notifications'
AppRegistry.registerComponent('testApp', () => App)
AppRegistry.registerHeadlessTask(
'RNFirebaseBackgroundMessage',
() => backgroundMessageNotificationHandler)
The backgroundMessageNotificationHandler could be something like that:
export const backgroundMessageNotificationHandler = async (message: any) => {
const localNotification = new firebase.notifications.Notification().android
.setChannelId(androidNotificationsChannel.channelId)
.android.setSmallIcon('iconBlackAndWhite')
.android.setLargeIcon('iconBlackAndWhite')
.android.setPriority(firebase.notifications.Android.Priority.High)
.setNotificationId(message.messageId)
.setSound('default')
.setTitle(message.data.title)
.setBody(message.data.body)
.setData(message.data)
if (Platform.OS === 'android') {
createChannel()
}
firebase.notifications().displayNotification(localNotification)
return Promise.resolve()
}
Remember to follow the steps to setting up the build.gralde, MainActivity and AndroidManifest. You can follow the steps specified in the following post:
React-Native Android Push Notification via Firebase

React native "Attempt to invoke virtual method 'android.app.Activity.showdShowRequestPermissionRationale' on a null object reference"

I'm trying to incorporate Twilio voice using the react-native-twilio-programmable-voice package. My app loads on ios, but when running on android I get this error message
Attempt to invoke virtual method 'boolean
android.app.Activity.shouldShowRequestPermissionRationale' on a null
object reference
screenshot here
I've included <uses-permission android:name="android.permission.RECORD_AUDIO" /> in AndroidManifest.xml
and none of the TwilioVoice related functions are called until 4 or 5 screens into the app.
Been scratching my head for a few days now, any help is greatly appreciated.
Code snippet of my Twilio helper class:
import TwilioVoice from 'react-native-twilio-programmable-voice';
import {Platform} from 'react-native';
import config from '../config/Config';
export default class Voip{
constructor(props) {
this.state = {
};
}
async setupDeviceWithToken(accessToken){
console.log('V32: setup device', accessToken);
TwilioVoice.addEventListener('deviceReady', () => this.deviceReadyHandler());
TwilioVoice.addEventListener('deviceNotReady', () => this.deviceNotReadyHandler());
TwilioVoice.addEventListener('connectionDidConnect', () => this.connectionDidConnectHandler());
TwilioVoice.addEventListener('connectionDidDisconnect', () => this.connectionDidDisconnectHandler());
if(Platform.OS === 'ios')
{
TwilioVoice.addEventListener('callRejected', this.callRejected());
} else if (Platform.OS === 'android')
{
TwilioVoice.addEventListener('deviceDidReceiveIncoming', this.deviceDidReceiveIncomingHandler());
}
var success;
try {
success = await TwilioVoice.initWithToken(accessToken);
console.log('V36: ', success);
//return success;
}
catch(err){
console.log('V40: ' ,err);
return err;
}
// if(Platform.OS === 'ios')
// {
try {
TwilioVoice.configureCallKit({
appName: 'VoipApp' // Required param
})
console.log('V50: ios success');
//return 'success';
}
catch (err) {
console.log('V54: ',err);
return err;
}
// }
return success;
}
I've been using the same library to handle the integration with React Native. I feel your pain with attempting this integration because there are a lot of hiccups along the way. I ran into this problem when letting the Android side handle permissions.
I discovered that permissions can instead be handled through React Native on Android. I commented out the permission calls on Android and switched over to the React Native implementation, and that worked for me.
There have been many updates since this question was posted so the user must take care in what versions are being used between Twilio Voice for iOS (Pods), Android, and the React Native wrapper. I ended up forking the library to control what is merged because every aspect is under active development.
async function requestMicrophonePermission() {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
{
'title': `${name} Microphone Permission`,
'message': `${name} needs access to your microphone
'so you can talk.`
}
)
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
console.log("Microphone permission granted")
} else {
console.log("Microphone permission denied")
}
} catch (err) {
console.warn(err)
}
}
Best of luck!
I had the same issue and after examining the code in detail, I figured out that the permission request is been made in TwilioVoiceModule's constructor and at that time, ActivityCompat might not have been initialized probably (BTW, I am an iOS developer and haven't worked much on Android). So I created a #ReactMethod that asks for permission and called that method in ReactNative after I get to the call screen.
Something like this:
#ReactMethod
public void askForMicrophones() {
if (!checkPermissionForMicrophone()) {
requestPermissionForMicrophone();
}
}
I have the same type of issue but slightly different Attempt to invoke interface method 'expo.modules.interfaces.barcodescanner.BarCodeScannerInterface expo.modules.interfaces.barcodescanner.BarCodeScannerProviderInterface.createBarCodeDetectorWithContext(android.content.Context)' on a null object reference
ℹ️ After downgrading expo-camera from 12.1.0 to 12.0.3, the issue does not seem to show anymore.
I solve this problem by starting a new react native project because it save me all the stress of looking for unknown solutions

Categories

Resources