React Native Expo Task Manager - react-native

Eventually, I would like to be able to run background tasks in my React Native app (Axios fetch to get some fresh data at least once a day). I am struggling to make this work:
https://docs.expo.io/versions/latest/sdk/task-manager/
import * as BackgroundFetch from 'expo-background-fetch';
import * as TaskManager from 'expo-task-manager';
const FETCH_TASKNAME = 'test_task'
const INTERVAL = 60
function test() {
console.log('function is running')
}
export async function registerFetchTask() {
TaskManager.defineTask(FETCH_TASKNAME, test());
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 === FETCH_TASKNAME) == null) {
console.log("Registering task");
await BackgroundFetch.registerTaskAsync(FETCH_TASKNAME);
tasks = await TaskManager.getRegisteredTasksAsync();
console.debug("Registered tasks", tasks);
} else {
console.log(`Task ${FETCH_TASKNAME} already registered, skipping`);
}
console.log("Setting interval to", INTERVAL);
await BackgroundFetch.setMinimumIntervalAsync(INTERVAL);
}
}
}
and calling this in App.js
import { registerFetchTask } from './helpers/backgroundFetch'
registerFetchTask();
I am getting a console logs up to this point:
function is running
Background execution allowed
Registering task
But I am unfortunately also getting following errors:
TaskManager.defineTask must be called during the initialization phase!
I read also in the documentation and as per example code, I am running in App.js directly and not in the component class.
And I am getting the following warning:
[Unhandled promise rejection: Error: Task 'test_task' is not defined. You must define a task using TaskManager.defineTask before registering.]
Which I don't understand since it is defined at the very top and before registering.
It is only unfortunate there is no proper working example anywhere to be found. An example would save countless hours for people struggling with this. Is there maybe an easier way of making background tasks running in react native apps?
Thanks so much for your kind help.

I can spot a couple of problems:
When you define the task, pass in a reference to the function rather than calling it:
TaskManager.defineTask(FETCH_TASKNAME, test); // no parentheses
registerFetchTask() is async which means it returns a promise when called in App.js. That probably doesn't count as the "initialization phase" of the app so try removing async
I don't know whether these changes will solve the problem but they should help.

change the line TaskManager.defineTask(FETCH_TASKNAME, test()); to
TaskManager.defineTask(FETCH_TASKNAME, test);
instead of passing the return value of the function pass the function reference.

Related

Error: record.prepareUpdate was called on ${this.table}#${this.id} but wasn't sent to batch() synchronously -- this is bad

I'm on react native and I'm unsure how to use prepareUpdate is it okay to do the following?
const oldChannel = await getChannel('asdf')
const prepareChannel = (x: Channel) => {
x._raw.id = 'asdf'
x.parent!.id = 'test'
}
const preparedChannel = oldChannel
? oldChannel.prepareUpdate(prepareChannel)
: channelsCollection.prepareCreate(prepareChannel)
await doSomeAsyncWork()
await database.write(() => database.batch(preparedChannel))
From the source code it says
// After preparing an update, you must execute it synchronously using
// database.batch()
Additionally at some point I'm pretty sure I got the error record.prepareUpdate was called on ${this.table}#${this.id} but wasn't sent to batch() synchronously -- this is bad! but I'm no longer able to reproduce that error also I have no idea how I got it because I'm on react native and process.nextTick is not defined which is needed for the error to appear.
https://github.com/Nozbe/WatermelonDB/blob/44d89925985aca3fa72eef1df78f89356b1d9b6f/src/Model/index.js#L118

Code not executing when awaiting dispatch in for loop

I can not find anything on this so I decided to ask it here.
I am using react-native and redux.
I left the business logic out of the code.
I have this function which calls another function that does an API call.
The function that does the API call needs to access "getState", that's the reason I use dispatch.
const getStuff = (day) => (dispatch) => {
const result = await dispatch(functionThatDoesAPIRequest(day)
}
Then I have this code that loops over days and in some cases needs to do the API call. In the case I was testing it does not need to access the API. I could not find the problem with my code until I placed the line between the if statement in comments. Then I changed the if to always be False and the error persisted when the line of code was not in comments, even though it could never reach that piece of code.
for (let i = 0; i < dayArray.length; i+=1) {
if (false) {
const resultThatIsNotNeeded = await dispatch(getStuff(dayArray[i])
}
// more business logic
}
Changing the for loop to a ForEach loop fixed this issue somehow. But I really have no clue why this fixed it.
dayArray.forEach(async (day, i) => {
if (false) {
const resultThatIsNotNeeded = await dispatch(getStuff(dayArray[i])
}
}
That is because on your forEach the await call is inside of a async function as it should be
On the for loop I don't see any of this code being executed within a async function

How to correctly utilise redux-saga putResolve

I'm trying to sequence my API calls so that I can call an endpoint, wait for it to succeed, then select the required value out of the state.
I'm very new to redux-saga and am finding it difficult to understand how to achieve this!
What I've tried:
So we try and block whilst we perform an API call:
const resp = yield putResolve(assistantActions.fetchSustainability());
Later in the code we select the updated value out of the state:
const assistant = yield select(getAssistant);
The action that is called looks like:
export const fetchSustainability = () => ({ type: FETCH_SUSTAINABILITY });
In the saga we:
yield takeEvery(FETCH_SUSTAINABILITY, fetchSustainability);
Then the function returns a promise:
function fetchSustainability() {
return axios.get(`${url}/sustainability`);
}
I'm not sure that I've set this up any where near right as I've made lots of changes trying to get it working!
The select statement executes immediately and as the call isn't blocked the state isn't updated correctly.
Is my approach correct? If not is someone able to provide a simple working example on how to achieve a blocking call.
So the second parameter to takeEvery needs to be a generator function;
within that you can call your fetchSustainability
function* generator() {
yield call(fetchSustainability); // calls the actual function
}
// in the saga
yield takeEvery(FETCH_SUSTAINABILITY, generator);
your action fetchSustainability should return a Promise.
Here's an example to highlight the difference between put and putResolve.
https://codesandbox.io/s/redux-saga-example-x77jb
The example uses thunkMiddleware

RN with Firestore unable to wait for Promise to resolve

I have a simple call to Firestore to write a doc and then wait for the doc to finish writing before changing state of the parent. However, the parent state is being changed too fast, resulting in reading fields that I think have not yet been written/propagated. I tried adding a delay with setTimeout and it seems ignored. How can I make sure the state change is absolutely only called after the Firestore doc is written completely?
The code:
updateDBEntry(stateObj) {
var that = this;
var docRef = firebase.firestore().collection('sessions').doc(this.state.userID);
docRef.get().then((doc) => {
if (!doc.exists) {
const timestamp = firebase.firestore.FieldValue.serverTimestamp();
var duration = (stateObj.seshDuration) ? stateObj.seshDuration : 1;
docRef.set({
seshName: stateObj.seshName,
seshStreet: stateObj.seshStreet,
seshZipcode: stateObj.seshZipcode,
seshDuration: duration,
seshDesc: stateObj.seshDesc,
seshTime: timestamp,
}).then(() => {
var handleToUpdate = that.props.handleToUpdate;
setTimeout(() => {
handleToUpdate(1); //this changes the parent's state
}, 10000);
});
}
});
}
I'm not sure exactly the problem you're running into here, mostly because you've only shown this one function, and not how you're using it in the rest of your app. But I can tell you three things for sure:
When the promise from set() resolves successfully, you can be certain the document is written.
get() and set() are asynchronous, and so is then(). They all return promises the represent async work.
Item 2 means that your entire function updateDBEntry() is also asynchronous and returns immediately, before any of the work is complete.
Because this entire function is async, when it returns, the document will not have been created yet. Instead, maybe this function should return that resolves only after all the work is complete, so that the caller can also use it set up some code to execute after the work is done.

How to initialize MobX store with async data (AsyncStorage) in React Native

We created a simple MobX store to save an object. This MobX store also syncs with AsyncStorage every time fooObject is changed:
import { observable } from 'mobx';
class FooStore {
#observable fooObject = {};
setFooObject(newFooObject) {
this.fooObject = newFooObject;
this.syncWithAsyncStorage();
}
syncWithAsyncStorage() {
AsyncStorage.setItem('fooObject', JSON.stringify(this.fooObject));
}
This mobx store is then consumed by various screens in the app.
As you can see, we use RN AsyncStorage, which is kind of a LocalStorage but for native apps, to always store the current state of fooObject.
The question is: when the app is closed, we loose FooStore state, so we want to retrieve the last 'save' we did to AsyncStorage (which is persisted).
The problem is AsyncStorage is (as the name states) an ASYNC function, and we can't wait for a promise resolution in FooStore's constructor.
How one would accomplish this initial state initialization of this MobX store based on the resolved return value of AsyncStorage?
We thought about writing an initialization method (async method) in FooStore like this:
async initializeFromAsyncStorage() {
this.fooObject = await JSON.parse(await AsyncStorage.getItem('fooObject'));
}
And then call this method on app initialization in our root App.js.
But I'm not sure if this would present unintended side effects and if this is idiomatic mobx/react-native.
Yes, I would create an initialization method. If the instance is not dependent of the initial data, you could call it right after the instantiation:
const fooStore = new FooStore()
fooStore.initializeFromAsyncStorage()
export default fooStore
Thill will not guarantee that the initial data is loaded when the app is constructed, but since the data is an optional observable, your app will react to it if/when it’s available.
Remember that AsyncStorage.getItem will resolve null if it has not been set, so you might want to do:
async initializeFromAsyncStorage() {
const result = await AsyncStorage.getItem('fooObject')
if (result !== null) {
this.fooObject = JSON.parse(result)
}
}