iOS sometimes loses angular variables when resuming from the background

I have a number of variables that get set when someone logs into my App, these then get used throughout the application.

The majority of the time this works great but sometimes on iOS devices (this doesn’t seem to affect Android) when the App is resumed from the background these variables are lost therefore causing errors.

Does anyone know why this happens or what the best solution is to the problem?

I do also store these variables in local storage so possibly some sort of getter/setter that can check if the value is null or undefined and if so pull it from local storage. If this is the case then I would imagine I will need to do quite a bit of reworking because I would need to wait for the local storage promise to return the value (using “then”) and I would not be able to use the current method where I am just referencing the variable directly (code examples below).

Below is an example of class I use to store the variables:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class GlobalProvider {

  // Global variables
  public authToken: string;
  public customerId: string;
  public version: any;
  public build: any;
}

The following shows how I set the “customerId” in local storage but also set it on the GlobalProvider class shown above:


  // This gets called when the user logs in
  this.authLocal.setCustomerId(1234);

  // This is the method that gets called above and sets the customer ID in 
 // local storage but also sets in on the GlobalProvider class
  setCustomerId(customerId) {

    return new Promise((resolve) => {
      this.storage.ready().then(() => {
        this.storage.set('customerId', customerId).then((value) => {

          // Set global variable
          this.global.customerId = value;

          // Return true
          resolve(true);
        });
      });
    });
  }

Below is how I then get and use the “customerId” throughout my App but as stated above, sometimes on iOS devices when the App resumes from the background this value is undefined:

const test = 'customerid=' + this.global.customerId;

Has anyone had this problem before or got any thoughts as the best practice to resolve it, ideally without having to put everything in a “then” after it has retrieved the value from local storage?

I would look at this as an opportunity, because the design you have now does not provide any way for GlobalProvider to signal changes in any of these variables. Incidentally, you should not be manually instantiating Promises as setCustomerId is currently doing: see this article for why. Also see this post for example code involving this general idiom.

@rapropos thanks for getting back to me so quickly, I thought you might be the first one to reply as you have often helped before on previous questions I have asked.

With regards to “setCustomerId” do you mean that I should just be doing something like this instead:

  setMasterCustomerId(masterCustomerId) {

      return this.storage.ready().then(() => {
        this.storage.set('masterCustomerId', masterCustomerId).then((value) => {

          // Set global variable
          this.global.masterCustomerId = value;

          return value;
      });
    });
  }

I believe what you are referring to might be similar to what I am using elsewhere in my App where I have the following service:

@Injectable({
  providedIn: 'root'
})
export class JobDataService {

  private jobSource = new ReplaySubject<Job>(1);
  currentJob = this.jobSource.asObservable();

  constructor() { }

  updateJob(job: Job) {
    this.jobSource.next(job);
  }
}

Then on every page that I want to use the “job” variable I need to have this:

  // Variables
  job: Job;
  jobSubscription: any;

  ionViewWillEnter() {
    // Subscribe to current job
    this.jobSubscription = this.jobData.currentJob.subscribe((job) => {

      // Set job variable
      this.job = job;
    });
  }

  ionViewDidLeave() {
    this.jobSubscription.unsubscribe();
  }

Then anywhere that I want to update the “job” variable on all the pages I have to call this:

this.jobData.updateJob(this.job);

Bearing in mind that the values in GlobalProvider never get updated, they only get set when the user first logs in and they get cleared if they logout (but they of course need to be available all the time they are using the App and when it resumes from the background), also they are used on pretty much every page and service within my App, it therefore seems like I would have to add a lot of extra code to get the method above to work and also are there any guarantee’s that the variable such as the “currentJob” one in the service above would not suffer from the same problem on iOS of it somehow getting cleared when the App resumes from standby?

I guess what I am asking is whether there is another way that doesn’t require adding so much extra code throughout my App or maybe I have not understood what you are suggesting properly and there is a better way of implementing it without adding the subscription to the data service on every page?

That’s an improvement, but if you’re following the idiom of “only read from storage at startup”, there’s no need to wait on the write to storage.

While there’s nothing wrong with the code here, another option for jobSource’s type would be BehaviorSubject<Job | undefined>. asObservable is also not needed: you can simply return jobSource as an Observable<Job | undefined>, tsc will take care of enforcing attempts to pierce the implementation veil. I also dislike providedIn: 'root' because it makes it impossible to mock the service out for testing purposes.

This is a good question, and why I recommend never exposing properties of services - only methods. If instead of allowing outside clients to access currentJob, if they had to go through a peekJob (which gives a snapshot) or watchJob (giving an Observable) method, then your service could have a chance to recognize the situation you’re running into, where its data has been lost out from under it, and recover seamlessly without the client ever being aware.

The second post l linked previously exposes the trio of peek/poke/watch operations that I find convenient. The way you are using the data now could be easily adapted to just call peek once at construction. This gives you a snapshot - the upside is that you don’t have to manage any subscriptions; the downside is that updates cannot be pushed.

If you’re using Capacitor, you could look at appStateChange to receive notifications of your app being paused and resumed, which might offer you opportunities to refresh things from storage on returning to the foreground.

Yes good point, I think when I wrote most of my write to storage methods a few years ago I probably just copied them from ones where I was reading from storage where I, of course, need to wait for it to read, I do have a couple where it writes that I do want to wait because I need to be sure the data has been written to local storage before the next action happens to prevent any data loss but on this occasion, as you say there is no need to wait.

Okay I will take a look at that, I wrote this method recently when migrating from Ionic 3 to Ionic 5 based on a recommendation (within a blog post) of using ReplaySubject rather than BehaviourSubject but I cannot remember the reasons they suggested ReplaySubject instead!

That makes sense so I guess it would mean modifying your peekJob example to check if it was undefined and if so return it from local storage? I assume this wouldn’t cause a problem the fact that local storage would be returning a promise that I would need to wait for?

Do you recommend calling these within the “constructor” as opposed to within “ionViewWillEnter”? If so what is the reason for this? Do we know if the “constructor” of a page gets called when the App resumes from the background which I think “ionViewWillEnter” does? My concern is that the peek/poke/watch etc will also get lost when iOS does whatever it is doing that is causing my problem.

I am not using Capacitor but I do have the following within app.component.ts which I assume would do the same thing?

// When App resumes from background
    this.platform.resume.subscribe(() => {
      // do the stuff
   }

Peek would return Job | undefined, so I guess at that point it becomes incumbent on the caller to be able to deal with undefined. It seems more and more that the situation you describe really would be better served with watch as opposed to peek.

That depends on what they’re used for. I have the following three options enabled in tsconfig.json in all my Ionic (and Angular) projects:

    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictPropertyInitialization": true

If I leave any object properties declared but not initialized, it’s a compile-time error. There are situations where I can’t initialize at the point of declaration, such as needing to call a method from an injected service. Those must be done in the constructor in order to keep strictPropertyInitialization happy.

Yes, I think that would be equivalent, and that resume handler is hopefully going to be the key to all of this, as it seems the most likely pressure point to rebuild whatever iOS has evicted from memory.

I think I may have found the article or at least one similar to what made me set up my GlobalProvider the way that I have:
https://stackoverflow.com/questions/43991306/angular-4-5-6-global-variables
https://stackblitz.com/edit/angular-x32any

The “Job service” is a different matter because this is read and updated by various pages and can probably stay how I currently have it (or be changed to your watch, peek, poke solution if necessary).

If I was to continue using my current GlobalProvider solution which is pretty much exactly what is suggested in the link above but added something to my “App resume from background” method to repopulate the global provider variables from local storage would that be a problem?

Is there some fundamental downside to doing it that way? I have no problem rewriting my App if the solution you suggested is better or what the link above suggests is not best practice or could cause potential issues but I just wanted to be clear on the reasons behind doing it first as it is, of course, going to take some time for me to do and result in additional code on each page/service to be able to read my global variables whereas currently, I can simply add the following on any page/service to get what I need:

import { GlobalProvider } from '../services/global';

constructor(
    public global: GlobalProvider
  ) {}

let customerId = this.global.customerId;

Just to be clear I am not questioning your solution but I just want to know if there is a major downside/upside from using one solution over the other before I convert from doing it the current way to the way you have suggested?

Theoretically, there could be a race condition on resumption where the repopulation hasn’t finished yet, but clients are fetching and holding undefined out of the global variables. It sounds like this is exactly what you are observing, so maybe it’s not just theoretical for you at this point.

You would at least need some sort of indicator that GlobalProvider is safe to use, such as the ready() Promise that is conventional in Ionic things like Platform and Storage. If you’re going to bother with a ready Promise, it seems like one might as well just make all the globals futures (Promise or Observable) anyway.

The main one in my experience is the topic of this thread: what to do when the values aren’t constant. Both “what do I do when the globals change for business logic reasons (like ‘the user’s session timed out’)?” and “how do I make consumers wait until the globals are ready, even if I’m not planning on changing them later?” can be thought of as subsets of that problem.

So I would say that the design you have now would be appropriate for constants that are known at compile time, such as baked-in branding and logos in a whiteboxed app. For anything that is more dynamic, including anything that needs to be fetched from storage or a network, you’ll eventually have to confront in some form or another the situation where the data isn’t truly “constant” in a sufficiently strict sense to allow for bare-metal access to it from outside the object that holds it.

Okay thanks, so in terms of getting the variables from local storage and waiting to make sure they have a value before doing something, how do I go about doing that?

These variables are used in API calls so it is not a case them just being displayed on a HTML page once they become available, they need to be present before making a call to an API where they are passed through.

So one question is, can I put something in your watch, peek, poke service that does the null check and then pulls from local storage if it is not present, or would the part where it gets from local storage have to be within the constructor (or “ionViewWillLoad”) of the page, so I would have to subscribe to the “watch” and have an “if” statement where it checked if it was null and if so it got the value from local storage and “poked” it back to the service (so that it shouldn’t be null the next time a page wants to get it)? If this is the case then it would mean a lot of duplicate code on each page doing the local storage check.

The second question would be how do I make the API call on the page wait until there definitely was a value available, assuming the paragraph above is true would it mean that on each page I would have to subscribe to the “watch”, check that the value was not null and if so get from local storage, then subscribe to that as well and only then make the API call with the value that was retrieved (either from the watch or from local storage)?

I haven’t tested this code yet but is the following anywhere close to a good solution?

I would still need to add something in the “watchJob” method that “poked” the job if it got it from local storage so that next time it hopefully wouldn’t have to call local storage again:

export class JobDataService {

  constructor(
    private jobsLocal: JobsLocalService
  ) { }

  job = new BehaviorSubject<Job | null>(null);

  watchJob(): Observable<Job | null> {
    if (this.job != null) {
      return this.job;
    } else {
      return from(this.jobsLocal.getCurrentJob());
    }
  }

  peekJob(): Job | null {
    return this.job.value;
  }

  pokeJob(job: Job) {
    this.job.next(job);
    this.jobsLocal.setCurrentJob(job);
  }
}
export class JobsLocalService {

  constructor(
    private storage: Storage
  ) { }

   
    // Set current job in local storage
    setCurrentJob(job) {
      this.storage.ready().then(() => {
        this.storage.set('currentJob', job);
      });
    }

    // Get current job from local storage
    getCurrentJob() {
      return this.storage.ready().then(() => {
        return this.storage.get('currentJob');
      });
    }
}

Then in the pages that I need to get the current “job” the following:

  ionViewWillEnter() {

    this.job = this.jobData.watchJob();

    this.job.subscribe(() => {
      // Get viewable custom attributes
      this.getViewableCustomAttributes();
    });
  }

Looks generally OK. A couple of comments: are you seeing in the real world that even your singleton services (JobDataService here) are silently losing variables, not just pages? That I would find surprising and disconcerting. If that isn’t true, and you don’t have compelling reasons for making watchJob lazy, I would simply:

constructor(private jobsLocal: JobsLocalService) { 
  jobsLocal.getCurrentJob().then(cj => this.job.next(cj)); 
}

watchJob(): Observable<Job | null> { return this.job }

…and:

this.job = this.jobData.watchJob();

I would not keep a reference to the Observable here, but you do need to worry about cleaning up the subscription. I use ngneat/until-destroy for this, but there are other options.

I am not too sure to be honest, currently, in my “job-summary” page I have the following:

  ionViewWillEnter() {

    // Subscribe to current job
    this.jobSubscription = this.jobData.currentJob.subscribe((job) => {

      // Set job variable
      this.job = job;

      if (this.job == null) {
        throw new Error('Job is NULL in ionViewWillEnter of job-summary');
      }

      // Get viewable custom attributes
      this.getViewableCustomAttributes();
    });
  }

  ionViewDidLeave() {
    this.jobSubscription.unsubscribe();
  }

With my JobDataService still looking like this at the moment:

export class JobDataService {

  private jobSource = new ReplaySubject<Job>(1);
  currentJob = this.jobSource.asObservable();

  constructor() { }

  updateJob(job: Job) {
    this.jobSource.next(job);
  }
}

I have earlier today seen the error occur after the App resumed from the background when it was left on the “job-summary” page, the error was that “this.job” was “undefined”, it also did not throw the “Job is NULL in ionViewWillEnter of job-summary”, which suggests to me that “ionViewWillEnter” doesn’t seem to run when the App resumes from the background, also that it has of course lost the “this.job” variable in the “job-summary” page and potentially lost the original subscription to the JobDataService that was setup in “ionViewWillEnter” (maybe ionViewDidLeave is sometimes getting called when the App goes into the background, but ionViewWillEnter does not get called when it comes back?).

Do you think switching to the JobDataService method and “watchJob” you suggested in your last post could potentially resolve this?

It is a tricky one because as mentioned before I cannot replicate this error locally so it is a case of changing some code, deploying it live and then waiting for the error to occur on the of the end-users devices to confirm whether or not the problem still exists, which is of course not ideal.

This is great detective work. One further point of clarification: are tabs involved here at all, or does each page fully “get the limelight” when it is active?

Yes tabs are involved.

I have a tabs page called “job” (this also subscribes to the “currentJob” because the job page contains a “canDeactivate” method to make sure the user has saved before exiting and it also contains the “saveJob” method which is called from the child tab pages using an event subscription), then “job-summary” is one of the child tabbed pages.

Ugh. Then definitely read the whole thread I linked to in my first post in this thread, because said thread is called “Ionic 4 Tab to page then back to Tab did not trigger ionViewWillEnter”.

I assume from your response you are not too fond of tabs?

I have just re-read through that post and unfortunately I am still no wiser as to how this can be resolved.

There is your post in there about using watch, peek, poke, but is there a difference between that way of subscribing to the data service within the “constructor” than there is with the current subscription method I am using within “ionViewWillLoad”?

My current method works initially but something falls over when resuming from the background (again, this only happens sometimes and I cannot replicate it no matter what I have tried).

Is there anything to suggest that using your “watch” method within the “constructor” would not suffer from the same problem, as I assume the “constructor” method would also not run when the app resumes from the background?

Or does your method behave differently to the one I am currently using? I can see in yours that it seems you do not set the page “job” variable withing a “subscribe” like I am, you seem to set it by just calling “watch…” so in my situation something like “this.job = jobData.watchJob();”

It’s a complicated subject. I see lots of posts here that use tabs in ways that I consider inappropriate, but that’s a different issue. Regarding your issue, I just know that Ionic lifecycle events are not (maybe “have not historically been”, I haven’t bothered to check in a long time) guaranteed to fire when one switches back and forth amongst tabs, so I would not rely on ionView*** for anything in a tab-hosted page.

I would subscribe in ngOnInit and unsubscribe in ngOnDestroy. I would also put console logging statements in both those places so that you can see when they are being called, and then put your app through the “let it sit on job-summary until paused, then resume” test. Similarly, put console logs in your pause and resume handlers so you know when those are being called. I would put a button in the “job-summary” page that prints to console what the current value of job and jobSubscription are in the page. Press the button before and after the pause/resume cycle and compare the results.