I’ve finally decided to implement this crazy workaround to handle the cordova lifecycle in case the android device run on low memory
Here are some keys of the implementation:
-
It’s important to note that you could play with the quality of the photo and the size to reduce the memory used. Same for your application. But at some point, you can’t be sure that none of your users gonna face this problem. That’s why I have decided to implement this.
-
I don’t wanted to have a side effect on my iOS bundle which just works fine, therefore I mostly added the code under check of which platform is running aka this.platform.is('android')
-
This workaround isn’t a nice user experience, therefore, at least, the user should not see the app navigating by herself. To do so, you have to take care manually of the splash screen respectively display your splash screen till your app have handled the restart and reloading the image and then hide manually the splash screen splashScreen.hide()
-
To easily reproduce the problem, you have to set “always destroy activities” to true in the developer settings of your Android phone. To face the real problem on my Nexus 5X with Android 8.1.1, I just run one or two other apps in the same time as mine, like Spotify
Pseudo-code:
-
Where you are using the camera, before taking a picture, you have to save in the storage the current state of the object(s) you are modifying. In my case let’s say I want to add a photo
to an object
private saveForRecovery(): Promise<{}> {
return new Promise((resolve) => {
if (this.platform.is('android')) {
this.storage.set('key', this.myObject).then((recover: any) => {
resolve();
});
} else {
resolve();
}
});
}
takePhoto() {
this.saveForRecovery().then(() => {
this.camera.getPicture(options).then((imageURI: string) => {
// Handle imageURI as usual
});
});
}
-
Now you have to handle the restart of the app respectively handle the restart of the app after android had put it in the background and to check if this happens after having take a photo and if the image URI is provided as fallback. Furthermore, if this happens, save the URI in a provider to be handled. To do so modify app.component.ts
for example
Note: If you are debugging with “always destroy activities” set to true, the status of the pendingResult gonna be ‘OK’ and not ‘KO’. Just for testing purpose change 'OK' !== status
with 'OK' === status
constructor(platform: Platform, myService: MyService) {
if (this.platform.is('android')) {
this.platform.resume.subscribe((event:any) => {
this.handleAndroidCameraRestart(event)
});
}
}
private handleAndroidCameraRestart(event: any) {
if (event && event.pendingResult) {
const status: string = event.pendingResult.pluginStatus !== null ? '' : event.pendingResult.pluginStatus.toUpperCase();
if ('Camera' === event.pendingResult.pluginServiceName && 'OK' !== status && event.pendingResult.result !== '') {
this.myService.saveAndroidPhotoRecoveryURI(event.pendingResult.result);
}
}
}
- In my app the login is mandatory and I process the automatic login once this app is ready. Since I want to handle the restart, I had to improve my login to check if we recover from a crash. To do so, I check if I have an URI (image) to recover and furthermore if I found data in the storage to recover my object(s) too.
If it’s the case. Then you will have to navigate thru your app. In my case I have to do a setRoot
, then there to do the same check and slide
my slider and then do again the same check to open the modal
where I take the photo
Something like
this.platform.ready().then(() => {
let uri: string = this.myService.getAndroidPhotoRecoveryURI();
if (uri !== '') {
this.storage.get('key').then((myObject: any) => {
if (myObject) {
// Ok we have an URI and something in the storage, we could recover
} else {
// We can't recover we've got nothing in the storage, therefore process as usual. That gonna sucks for the user and you may lost him/her forever
}
});
} else {
// process as usual
}
});
- So finally you were able to open the right page/modal in you app, now you just have to load the imageURI and hide the splash screen.
It’s also important to reset the value in the provider and in the storage, you don’t want this all workaround to be processed again in case the user restart the app manually.
if (this.platform.is('android') && this.myService.getAndroidPhotoRecoveryURI() !== '') {
// this.imgURI is the URI I use in the html page to display the photo
this.imgURI = this.myService.getAndroidPhotoRecoveryURI();
this.myService.saveAndroidPhotoRecoveryURI(null);
this.storage.remove('key').then((whatever:any) => {
this.splashScreen.hide();
});
}
That’s it, you have build a gigantic kind of ugly workaround. At least now, if a user is running an android on low memory, if the app restart after the the user have taken a photo, he won’t have the feeling he lost everything, just the feeling that the app is a bit shitty because it took a while to process it.
Finally, I hope that in the future, live with capacitor, it will be possible to avoid this workaround.