Is there a way to monitor when a phone has their location settings (not permissions) turned on or off?
I know there's a way to check with react-native-device-info with the code below, but is there a way to add a listener to constantly check?
DeviceInfo.isLocationEnabled().then((enabled) => {
// true or false
});
When users change location setting, Users open setting page. It means you should know if app is foreground or background.
In that case, you can use AppState (https://reactnative.dev/docs/appstate).
To monitor when a phone has their location settings (not permissions) turned on or off, You can implement like this.
import {AppState} from 'react-native';
useEffect(() => {
const subscription = AppState.addEventListener('change', (nextAppState) => {
// if app is foreground.
if (nextAppState === 'active') {
DeviceInfo.isLocationEnabled().then((enabled: boolean) => {
// true or false
});
}
});
return () => {
subscription.remove();
};
}, []);
Related
Description:
When the app is running in a background state or Locked, When the user unlock or foreground the app, useNetInfo(); hook return as isConnected as false. Even I tried to re-fetch the state still using NetInfo.fetch() return the same state.
It's happening in Android real device connected to Wifi
Package Name:
"#react-native-community/netinfo": "^7.1.2",
Code:
const netInfo = useNetInfo();
const [show, setShow] = useState(false);
useEffect(() => {
setShow(!(netInfo.isConnected && netInfo.isInternetReachable));
}, [netInfo]);
useEffect(() => {
fetchConnection();
}, []);
const fetchConnection = () => {
NetInfo.fetch().then((state: any) => {
setShow(!(state.isConnected && state.isInternetReachable));
});
};
I fixed this issue by reverting the package version into "#react-native-community/netinfo": "5.9.7",
Also, change the androidXCore version into 1.6.0. Now it's working as expected.
Reason:
Due to the hibernation features changes in androidXCore version 1.7.0. Netinfo does not return the state properly if the app is in a hibernation state. Please fix this issue in the upcoming release. Thanks.
add navigation focus event listener and check for network update whenever the screen is focused.
Please try this, it's from the package npm site
useEffect(() => {
const subAppState = AppState.addEventListener("change", async (nextAppState) => {
if (IS_IOS_DEVICE && nextAppState=='active') {
let newNetInfo = await NativeModules.RNCNetInfo.getCurrentState('wifi');
//your code here
}
});
const unsubNetState = NetInfo.addEventListener(state => {
//your code here
});
return () => {
if (subAppState) {
subAppState.remove();
}
unsubNetState();
};
},[]);
My app asks permission to use location services.
If a user denies permission, they can click a button to go to the settings page and grant permissions.
On ios, they are given the option to return directly to the app. on Android, I think they can do something similar.
Is there a way to detect arriving back at the app so I can check their permissions again?
I've tried with React Navigation's useFocusEffect hook:
useFocusEffect(
React.useCallback(() => {
console.log("navigated")
return () => getPosition()
}, [])
)
But unfortunately, that only works when navigating between screens/routes in the app.
Is there a way to detect the app transitioning from background to foreground?
You can track app state with https://reactnative.dev/docs/appstate
in pseudo-code: if previously 'background' and now 'active' then run your effects
Ciao, when I want to manage permission, I use react-native-permissions. In this way you can manage all permissions directly in your app without exit from it.
Works well for iOs and Android. In your case, you could something like:
import {check, PERMISSIONS, RESULTS} from 'react-native-permissions';
check(<permission you need>)
.then((result) => {
switch (result) {
case RESULTS.UNAVAILABLE:
console.log(
'This feature is not available (on this device / in this context)',
);
break;
case RESULTS.DENIED:
console.log(
'The permission has not been requested / is denied but requestable',
);
break;
case RESULTS.GRANTED:
console.log('The permission is granted');
break;
case RESULTS.BLOCKED:
console.log('The permission is denied and not requestable anymore');
break;
}
})
.catch((error) => {
// …
});
Use this hook:
// hooks/useAppIsActive.ts
import { useCallback, useEffect, useRef } from "react";
import { AppState } from "react-native";
export default (callback: Function) => {
const appStateRef = useRef(AppState.currentState);
const handleAppStateChange = useCallback((nextAppState) => {
if (
appStateRef.current.match(/inactive|background/) &&
nextAppState === "active"
) {
callback();
}
appStateRef.current = nextAppState;
}, []);
useEffect(() => {
AppState.addEventListener("change", handleAppStateChange);
return () => {
AppState.removeEventListener("change", handleAppStateChange);
};
}, []);
};
And from the component you want to detect "the come back", pass by parameter the callback you want to run:
const MyView = () => {
const requestLocationAccess = useCallback(() => {
// request permissions...
}, [])
useAppIsActive(() => requestLocationAccess());
}
I am implementing FCM notifications in an Ionic React application. I am having trouble navigating to another page to display the notification details.
I have created a FCMService class in my react App, and initialising this in the index.ts file.
// FCMService.ts
export default class FCMService {
public static Instance: FCMService;
private _store: Store<IAppState>;
constructor(store: Store<IAppState>) {
this._store = store;
}
public static Initalise(store: Store<IAppState>) {
if (!FCMService.Instance) {
FCMService.Instance = new FCMService(store);
FCMService.Instance.InitaliseFCM();
FCMService.Instance._store.subscribe(() => { console.log(store.getState()) });
} else {
console.debug("FCM service already intialised. Please use FCMService.Instance");
}
}
private InitaliseFCM() {
// 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.requestPermission().then(result => {
console.log(result);
if (result.granted) {
// Register with Apple / Google to receive push via APNS/FCM
PushNotifications.register();
} else {
// Show some error
}
});
// On success, we should be able to receive notifications
PushNotifications.addListener('registration', (token: PushNotificationToken) => {
console.log(token);
localStorage.setItem("FCM_TOKEN", token.value);
}
);
// Some issue with our setup and push will not work
PushNotifications.addListener('registrationError',
(error: any) => {
console.log(error);
}
);
// Show us the notification payload if the app is open on our device
PushNotifications.addListener('pushNotificationReceived',
(notification: PushNotification) => {
console.log(notification);
let data = notification.notification.data as INotificationData;
}
);
// Method called when tapping on a notification
PushNotifications.addListener('pushNotificationActionPerformed',
(notification: PushNotificationActionPerformed) => {
console.log(notification);
let data = notification.notification.data as INotificationData;
this._store.dispatch(setNotificationActionCreator(data));
}
);
}
}
and then the index.ts
const store = configureStore();
interface MainProps {
store: Store<IAppState>;
}
FCMService.Initalise(store);
ReactDOM.render(<Provider store={store}><App /> </Provider>, document.getElementById('root'));
serviceWorker.unregister();
I even tried using the Redux store to save the notification on Tap - and then that would publish the notification change event (which might of worked - if I could access the useHistory() hook in the App.tsx file)
This was my attempt at navigating via Redux store in App.tsx
const App: React.FC<IProps> = ({ getCompanies, getUser, notification }) => {
console.log('app');
console.log(process.env);
const history = useHistory();
if(notification){
history.push(`/page/plot-position/{notification.id}`);
}
return (
<IonApp>
<IonReactRouter>
<IonSplitPane contentId="main" when="false">
<Menu />
<IonRouterOutlet id="main">
<Route path="/login" component={LoginPage} exact />
<PrivateRoute path="/page/plot-position/:notificationId/" component={PlotPositionPage} exact />
<Redirect from="/" to="/login" exact />
</IonRouterOutlet>
</IonSplitPane>
</IonReactRouter>
</IonApp>
);
};
const mapStateToProps = (store: IAppState) => {
return {
user: store.user.user as UserDTO,
notification: store.notificationState.notification
};
};
const mapDispatchToProps = (dispatch: any) => {
return {
getCompanies: () => dispatch(getCompaniesStartActionCreator()),
getUser: () => dispatch(getUserStartActionCreator())
}
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
It looks like your navigation works, but you're having trouble passing the notification object through to the page? You can pass the object through history state.
To access the useHistory hook you would need to make your FCMService a custom hook.
const useFCMService = (): void => {
const history = useHistory();
React.useEffect(() => {
// Method called when tapping on a notification
PushNotifications.addListener('pushNotificationActionPerformed',
(action: PushNotificationActionPerformed) => {
const notification = action.notification.data as INotificationData;
history.push({ pathname: '/page/plot-position/', state: { notification } });
}
);
}, []);
}
And then include your useFCMService custom hook in your App component.
const App: React.FC<IProps> = ({ getCompanies, getUser }) => {
useFCMService();
...
};
Deep linking provides us a way to do this: Using both an action to open the application and an action at opening the application we can enroute the user to the correct destination.
Opening the application
Here we will create an action to open the url when the user taps on the push notification; to do this less use a listener:
const {PushNotifications, App} = Plugins
***
PushNotifications.addListener(
"pushNotificationActionPerformed",
(notification: PushNotificationActionPerformed) =>{
const data = notification.notification.data;
if (data.packageNumber) App.openUrl({url: `com.company.appname://tabs/package-details/${data.packageNumber}`})
else App.openUrl({url:'/tabs'})
}
)
com.company.app:// is of capital importance since the app must reach the application must reach an existing given url, otherwise the following action(catching the url) won't be triggers since it waits a complete true from the App.openUrl function; as we are opening an internal url, this must begin with the apps given name in the capacitor config page(see the following example where we can realize how use the local url).
In this way we are adding a function to open the application in an specific route.
Redirecting the user
Here, we will complete the application's part from the deep linking tutorial: we create a new listener component who handles the appOpenUrl events and redirects to the user and we will put it on the main App file inside of its respective IonRouter:
const AppUrlListener: React.FC<any> = () => {
let history = useHistory();
useEffect(() => {
App.addListener('appUrlOpen', (data: any) => {
const slug = data.url.split(':/').pop();
if (slug) {
history.push(slug);
}
});
}, []);
return null;
};
Don't forget the route in router must begin with /, and since the application url contains :/, we split the url here and we get the second part, the slug; we push it on the history, triggering the router and getting the normal behaviour when you entering in a new route.
We will add this component inside of the router:
<IonReactRouter>
<IonSplitPane contentId="main">
<Menu />
<AppUrlListener />
<IonRouterOutlet id="main">
Now, the application will be listening the appOpenUrl event, and when it gets a new of this events, it will push the gotten url to the history, redirecting the user to that route.
i want to detect the current orientation of device in expo react native, this is my code that doesn't work:
import {
Dimensions,
} from 'react-native';
import * as ScreenOrientation from 'expo-screen-orientation';**
const App = () => {
...
useEffect(() => {
const isPortrait = () => {
const dimension = Dimensions.get('screen');
return dimension.height >= dimension.width;
};
Dimensions.addEventListener('change', () => {
const orientation = isPortrait() ? 'portrait' : 'landscape';
console.log('Dimensions orientation', orientation);
});
ScreenOrientation.addOrientationChangeListener((e) => {
console.log('e ', e);
});
}, []);
how ever when i rotate the device there is no logs so it's not firing?
This works for me:
const [orientation, setOrientation] = useState(
ScreenOrientation.Orientation.PORTRAIT_UP
);
useEffect(() => {
// set initial orientation
ScreenOrientation.getOrientationAsync().then((info) => {
setOrientation(info.orientation);
});
// subscribe to future changes
const subscription = ScreenOrientation.addOrientationChangeListener((evt) => {
setOrientation(evt.orientationInfo.orientation);
});
// return a clean up function to unsubscribe from notifications
return () => {
ScreenOrientation.removeOrientationChangeListener(subscription);
};
}, []);
You should set your orientation field as default in your app.json / app.config.js. The app is locked to the specified orientation if this field is set to another value.
Related doc is here:
https://docs.expo.dev/versions/v46.0.0/config/app/#orientation
This is the line that doesn't do anything. Broken, bugged, POS? All of the above?
ScreenOrientation.addOrientationChangeListener((e) => {
console.log(e);
});
I had this same issue. The listener function was never firing.
Adding expo-sensors to my project seems to have fixed the callback for me. I think expo-screen-orientation might depend on expo-sensors
Steps for adding:
npx expo install expo-sensors
Rebuild your expo development client. (For me that command is eas build --profile simulator, but that will depend on your eas config)
After that, the listener callback function started firing.
Here's a code snippet of where I add the listener:
useEffect(() => {
ScreenOrientation.addOrientationChangeListener((e) => {
console.log(e)
})
}, [])
You're using the wrong package.
From the expo-screen-orientation docs:
Screen Orientation is defined as the orientation in which graphics are painted on the device. ... For physical device orientation, see the orientation section of Device Motion.
reading from the expo docs:
For iOS, you would be wise to handle push notifications that are
received while the app is foregrounded, because otherwise the user
will never see them. Notifications that arrive while the app are
foregrounded on iOS do not show up in the system notification list. A
common solution is to just show the notification manually. For
example, if you get a message on Messenger for iOS, have the app
foregrounded, but do not have that conversation open, you will see the
notification slide down from the top of the screen with a custom
notification UI.
What I don't understand is what is the best approach for that? is there an Expo API for showing such messages? or should I create an alert component of my own? It is not really clear from the docs.
Thanks.
This answer is outdated as of February 20, 2020. Please see https://stackoverflow.com/a/60344280/2441420 for how to show iOS Notification when your application is in the Foreground
There isn't an Expo API for showing those messages. You can use any 'toast' library of your choosing and display the notification message, but that should be all your code.
For example, this is how we are doing right now:
export default class HomeScreen extends React.Component {
componentDidMount() {
this.notificationSubscription = Notifications.addListener(
(notification) => this.handlePushNotification(notification),
);
}
handlePushNotification(notification) {
const { navigation } = this.props;
PushNotificationsService.handleNotification(notification, navigation);
}
(...)
import Toast from 'react-native-root-toast';
export default class PushNotificationsService {
static handleNotification(notification, navigation) {
if (notification.data.screen && notification.origin === 'selected') {
navigation.navigate(notification.data.screen);
}
Toast.show(notification.data.message);
}
}
Toast libraries include:
react-native-root-toast
react-native-easy-toast
react-native-simple-toast
Now you can just add that in one of your app entry point. The shouldShowAlert is what you want here
import * as Notifications from 'expo-notifications';
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: false,
shouldSetBadge: false,
}),
});
App.json :
{
"expo": {
"notification": {
"iosDisplayInForeground": true
}
}
DEMO
I'm not sure exactly when this was added to Expo, but as of Expo version 36 is easily doable.
To show Expo Push Notifications on iOS when your app is in the foreground, please do the following:
import { Vibration } from "react-native";
import { Notifications } from "expo";
import * as Permissions from "expo-permissions";
import Constants from "expo-constants";
registerForPushNotificationsAsync = async () => {
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 token = await Notifications.getExpoPushTokenAsync();
console.log("Go to https://expo.io/notifications and copy the token below to easily send yourself a notification.");
console.warn("Notifications on iOS (and I believe Android) ONLY WORK ON A PHYSICAL DEVICE, not a simulator or emulator!!!")
console.log(token);
this.setState({ expoPushToken: token });
} else {
alert("Must use physical device for Push Notifications");
}
};
componentDidMount() {
this.registerForPushNotificationsAsync();
this._notificationSubscription = Notifications.addListener(
this._handleNotification
);
}
_handleNotification = async notification => {
if (notification.remote) {
Vibration.vibrate();
const notificationId = Notifications.presentLocalNotificationAsync({
title: "Follow #technoplato",
body: "To learn yourself goodly (also follow PewDiePie)",
ios: { _displayInForeground: true } // <-- HERE'S WHERE THE MAGIC HAPPENS
});
}
};
Quick and Easy Sanity Check
1) Go here: https://expo.io/notifications
2) Copy the token that is output to the terminal when your application is run.
3) Open your application on iOS.
4) Send a notification to yourself from https://expo.io/notifications and observe that it shows up even when your app is foregrounded.
Notes
Notifications WILL NOT BE RECEIVED ON AN IOS SIMULATOR
Expo makes Notifications ridiculously easy. I honestly can't believe it.
No idea why displayInForeground is false by default and not more prominent in the documentation. I'll submit a PR for it if I can.
Code originally found at this Snack: https://snack.expo.io/#documentation/pushnotifications?platform=ios
LocalNotification.ios._displayInForeground found here: https://docs.expo.io/versions/v36.0.0/sdk/notifications/#localnotification