React Native - How to access promise values from TaskManager in Expo? - react-native

I am testing background location tasks in react native with Expo's taskmanager to test when I enter a geofence. I've got the basics working but I am now trying to access the values within a promise that is returned from one of the tasks so i can display if you've entered or exited the geofence. Can anyone help me figure out how to properly access the values I want?
Below is the task portion relevant to the geofence. I return the value region which comes as a promise. I haven't been able to figure out how to send directly from task manager yet:
// 3 Define geofencing task
TaskManager.defineTask(TASK_CHECK_GEOFENCE, ({ data: { eventType, region }, error }) => {
if (error) {
// check `error.message` for more details.
return;
}
if (eventType === Location.GeofencingEventType.Enter) {
console.log("You've entered region:", region);
//this.props.setEnterRegion({ inRegion: region })
const final = region
return final
} else if (eventType === Location.GeofencingEventType.Exit) {
console.log("You've left region:", region);
const final = region
return final
}
});
Then within App.js I call the app in componentDidMount:
componentDidMount() {
this.getLocationsPermissions();
this.startBackgroundUpdate();
this.startGeofence();
}
//define and start geofence
startGeofence = async () => {
console.log('starting geofencing test ...')
let x = Location.startGeofencingAsync('TASK_CHECK_GEOFENCE',
[
{
identifier: 'court',
latitude: this.state.myLocation.latitude,
longitude: this.state.myLocation.longitude,
radius: 20,
notifyOnEnter: true,
notifyOnExit: true,
}
]
)
this.sendTask(x)
};
When I try to access the value I can see the promise and I am unsure where I am going wrong. I would perfer to send to the store directly from taskmanager but I am having trouble with that:
//send to store from task
sendTask = (x) => {
console.log('entered region...', x)
this.props.setEnterRegion(x)
entered region... Promise {
"_40": 0,
"_55": null,
"_65": 0,
"_72": null,
}

Related

expo-location startLocationUpdatesAsync triggered too rarely

This is where i trigger Location.startLocationUpdatesAsync
useEffect(() => {
if (isPermissionReady && destination && !intervalHandle.current) {
// Register location fetch to task manager
Location.startLocationUpdatesAsync(TASK_NAME, {
accuracy: Location.Accuracy.Balanced,
// activityType: Location.ActivityType.AutomotiveNavigation,
// deferredUpdatesTimeout: INTERVAL_MS,
})
// Repeatedly read local storage to update currentPos state
intervalHandle.current = setInterval(updateCurrentPos, INTERVAL_MS)
}
}, [isPermissionReady, destination])
And this is my TaskManager (declared separately in index.tsx not inside a lifecycle or hook):
TaskManager.defineTask(TASK_NAME, ({ data, error }) => {
if (error) {
console.error('Task Manager Failed')
return
}
if (data) {
const { locations } = (data as any) ?? { locations: [] }
const { latitude, longitude } = locations[0]?.coords ?? {}
try {
AsyncStorage.setItem(STORAGE_KEY, JSON.stringify({ latitude, longitude }))
} catch {
console.error('SetItem Failed')
}
}
})
My taskmanager reads location data from device and save to LocalStorage(AsyncStorage) and react native app fetches this data with setInterval. However, data set by TaskManager is updated too rarely and it is not called regularly in constant time(imo) and never called if I stay at the same place with all deferredOOO options disabled.
Does startLocationUpdatesAsync trigger only after some distance change even with default values? or am I doing something wrong? (I want it to be called in regular basis or at least have a clear understanding of when it is called)
Plus, is it normal for taskmanager to not show any console.log?

Expo Geofence exit event is always triggered upon startup

I am developing an app that would send data to my server based on the user entering and exiting a region using Expo Location. Currently testing on an Android emulator.
However, the exit event is triggered for all regions upon the app startup. Is this the expected behavior or am I doing something wrong?
Geolocation:
useEffect(() => {
(async () => {
await Location.startGeofencingAsync(GEO_LOC, region)
})();
return async() =>{
await Location.stopGeofencingAsync(GEO_LOC)
}
}, []);
TaskManager :
TaskManager.defineTask(GEO_LOC, ({ data: { eventType, region }, error }) => {
if (error) {
// check `error.message` for more details.
return;
}
if (eventType === Location.GeofencingEventType.Enter) {
console.log("You've entered region:", region.identifier);
socket.emit('court:increment', region.identifier)
}
if (eventType === Location.GeofencingEventType.Exit) {
console.log("You've left region:", region.identifier);
socket.emit('court:decrement', region.identifier)
}
});

React Native + Expo not working background location in some devices

I have an Android app that its built with React Native + Expo. My Expo is currently in version 39.0.0.
The user starts a location service by clicking in a button and when users changes position the app with its new position execute some rules based on location and show some notifications depending of user position.
Everything works great when the app is in foreground, in every tested devices. But, when the app goes to background it stops to work in some devices.
For example, in Xiaomi devices the app works great but in Samsung don't. =[
Can anyone help me?
Here`s part of my "app.json":
"android": {
"package": "br.i.dont.know.whats.going.on",
"versionCode": 86,
"permissions": [
"ACCESS_COARSE_LOCATION",
"ACCESS_FINE_LOCATION",
"ACCESS_BACKGROUND_LOCATION",
"CAMERA",
"READ_EXTERNAL_STORAGE",
"WRITE_EXTERNAL_STORAGE",
"NOTIFICATIONS",
"USER_FACING_NOTIFICATIONS",
"FOREGROUND_SERVICE"
],
This is the location task registered when app starts:
export async function registerFetchTask() {
TaskManager.defineTask(LOCATION_TASK, async ({ data: { locations }, error }) => {
if (error) {
console.error(error);
return;
}
const [location] = locations;
try {
if (location) {
AppState.currentState === "background" && updateChart(location);
}
} catch (err) {
console.error("Error while getting user location: ", err);
}
});
const status = await BackgroundFetch.getStatusAsync();
switch (status) {
case BackgroundFetch.Status.Restricted:
case BackgroundFetch.Status.Denied:
console.log("Background execution is disabled");
return;
default: {
console.debug("Background execution allowed");
let tasks = await TaskManager.getRegisteredTasksAsync();
if (tasks.find(f => f.taskName === LOCATION_TASK) == null) {
console.log("Registering task");
await BackgroundFetch.registerTaskAsync(LOCATION_TASK);
tasks = await TaskManager.getRegisteredTasksAsync();
console.debug("Registered tasks", tasks);
} else {
console.log(`Task ${LOCATION_TASK} already registered, skipping`);
}
console.log("Setting interval to", 5);
await BackgroundFetch.setMinimumIntervalAsync(5);
}
}
}
Task location started when user press the button
await Location.startLocationUpdatesAsync(LOCATION_TASK, {
accuracy: Location.Accuracy.Balanced,
timeInterval: 5000,
distanceInterval: 1, // minimum change (in meters) betweens updates
deferredUpdatesTimeout: 5000,
deferredUpdatesInterval: 5000, // minimum interval (in milliseconds) between updates
// foregroundService is how you get the task to be updated as often as would be if the app was open
foregroundService: {
notificationTitle: 'App is using your location',
notificationBody: 'To turn off, go back to the app.',
},
});

Stopping expo background location update gives the error "Task 'geofencing' not found for app ID"

I'm using React Native with Expo Location.startLocationUpdatesAsync. I have created three functions in the same file, the first two are in my export default App function, with TaskManager defined globally in the file.
async startBackgroundUpdate() {
await Location.startLocationUpdatesAsync(taskName, {
accuracy: Location.Accuracy.Highest,
timeInterval: 0,
distanceInterval: 0,
forgroundService: {
notificationTitle: "I'm a notification title",
notificationBody: "I'm a notification body",
notificationBody: "#abcdef"
}
});
}
async stopBackgroundUpdate() {
await Location.stopLocationUpdatesAsync(taskName)
}
TaskManager.defineTask(taskName, async ({ data, error }) => {
if (error) {
return console.log(error)
}
if (data) {
const { locations } = data;
locationGlobal = locations[0];
let date = new Date(locationGlobal.timestamp).toISOString().slice(-13, -5)
// App.setLocation(location)
console.log("Task has been running "+ count +" time(s). With data: "+data);
console.log(locationGlobal);
console.log(count++);
console.log(date)
}
});
I also have a global variable that holds the taskName
const taskName = 'geofencing'
The stop and start background location functions are hooked up to a button. I can start the background location fine, but when I try and stop it I get the error
Task 'geofencing' not found for app ID '#anonymous/track-it-fecc322d-0ade-4239-8bd1-fdcb47b0397e'.
- node_modules\react-native\Libraries\BatchedBridge\NativeModules.js:104:55 in <unknown>
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:414:4 in __invokeCallback
- ... 4 more stack frames from framework internals
I am new to this and I think I am missing a piece of the puzzle. I can see the taskName and create the task from inside my App function but at the same level I can't reference it to stop it, any ideas?

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