A project I am working on needs to use a long list of slow moving data, a list of exercises. This list is nearly static but not so much that I would want to hardcode it to local storage in this app and rely on future app releases to update it. I want to grab it from the FireBase, save it locally and then access it.
It will be updated occasionally as new exercises enter CrossFit or I fill in the blanks of ones I missed If this were a web app (or not running on FireBase that charges per read) I would just grab it from the server each time, however this is a mobile app made in Ionic so it’s best to read locally as much as possible and grab new data as required.
This is what we need to do:
Given that both steps 2 and 3 take time, this requires promises or RxJS, I went with the latter and built the following observable chain.
getAllExercises(): Observable {
if (this._exercises.length > 0) {
console.log('found in memory');
return of(this._exercises);
}
return this.getExercises();
}
If the service has already been loaded there’s a chance the static data will already be in memory, wrap that as an observable with of and return it. Otherwise lets go and get the exercises.
getExercises(): Observable {
return from(Storage.get({ key: 'exercises' })).pipe(
map(data => {
const exercises: Exercise[] = [];
if (data.value) {
console.log('found in local storage');
const ex = JSON.parse(data.value);
ex.forEach(element => {
exercises.push(new Exercise(element));
});
}
return exercises;
})
).pipe(switchMap(localExercises => {
if (localExercises.length > 0) {
console.log('returning from local storage');
return of(localExercises);
} else {
console.log('need to look remotely');
return this.getRemoteExercises();
}
}));
}
Ionic has a local storage plugin that we are making use of here, it’s for simple data saved to a key, in this case exercises (the Storage.get()returns a Promise to wrapping it in fromallows us to use it in this observable chain.
The first pipe (map) looks for the exercises key and returns what it finds. The second pipe (switchMap) takes a look at this data and if it has length greater than 0 it returns it as an observable. If the data isn’t there then we need to get it from FireBase.
getRemoteExercises(): Observable {
return this.dataStoreService.getAllExercises().pipe(
tap(exercises => {
this._exercises = exercises;
console.log('saved to local memory from remote');
}),
map(exercises => {
// save them locally
// TODO: come back and change from Fire and Forget to something safer
console.log('about to save to local storage');
Storage.set({
key: 'exercises',
value: JSON.stringify(exercises)
});
return exercises;
})
);
}
This calls the data service that handles the FireBase logic, it then saves the data to local storage with a fire and forget promise and then finally returns the data we wanted all along.
This code needs error checking and someway to check if there are new exercises in FireBase but, for the purposes of showing how to build a series of observable fallbacks, this does the trick.
This is called from the component code with:
this.wodService.getAllExercises().pipe(take(1)).subscribe(exercises => {
this.exercises = exercises;
});
Note the take(1)
handling our subscription.
Each read of this list will return over 100 objects, FireBase’s free tier is currently limited to 50,000 documents (object) reads per day, a busy user could easily take a 1,000 of those reads in a day just accessing this data, now the worst is the size of the list the first time they load the app.