Loading settings from Storage

I’ve been learning Ionic for the past week or so and for the most part am loving it. However I’ve bumped into an issue now which I have just not been able to get past.

The scenario is as follows: My app will connect to a URL and port number which is set by the user.

I have a SettingsProvider where I specify an AppSettings class which is created with the stored settings, this is then retrieved in the contrsuctor of the SettingsPage however the settings are never loaded when the page is opened. Both website_host and website_port are null. I have been able to verify that the data is stored as expected, the issue is just with getting the values when opening up the Settings page.

This is my SettingsProvider:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Observable'
import 'rxjs/add/observable/of';
import { Storage } from '@ionic/storage';

export class AppSettings {
    website_host: string = null;
    website_port: number = null;
}

@Injectable()
export class SettingsProvider {
    settings: AppSettings;

  constructor(private http: Http, private storage: Storage) {}
  
  public set(settingName,value){
    return this.storage.set(`setting:${ settingName }`,value);
  }
  public async get(settingName){
    return await this.storage.get(`setting:${ settingName }`);
  }
  public async remove(settingName){
    return await this.storage.remove(`setting:${ settingName }`);
  }
  public clear() {
    this.storage.clear().then(() => {
      console.log('Storage cleared');
    });
  }
  
  loadSettings(): Promise<boolean> {
        return new Promise((resolve, reject) => {
            var getPromises = [];
            this.storage.ready().then(() => {
                this.settings = new AppSettings();
                for (var settingName in this.settings){
                     getPromises.push(this.storage.get(settingName).then((val) => { (this.settings[settingName] = val);}));
                }
                
                Promise.all(getPromises).then((data) => {
                    console.debug("Settings loaded", this.settings);
                    resolve(true);
                });
            })
            .catch((error) => {
                console.log('Error loading settings from storage', error);
            });
        });
    }
    
    getSettings(): Promise<AppSettings> {
        return new Promise((resolve, reject) => {
            this.loadSettings().then(() => {
                resolve(this.settings);
            });
        });
    }

}

And this is my SettingsPage:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { SettingsProvider } from '../../providers/settings/settings'

@IonicPage()
@Component({
  selector: 'page-settings',
  templateUrl: 'settings.html',
})
export class SettingsPage {
    
    website_host: string;
    website_port: number;

  constructor(private navCtrl: NavController, private navParams: NavParams, private settingsProvider: SettingsProvider) {
      this.settingsProvider.getSettings().then((settings) =>{
          console.debug('Loaded settings',settings);
      });
      
  }
  
  public set(settingName,value){
    return this.settingsProvider.set(`setting:${ settingName }`,value);
  }
  public async get(settingName){
    return await this.settingsProvider.get(`setting:${ settingName }`);
  }
  
  public async remove(settingName){
    return await this.settingsProvider.remove(`setting:${ settingName }`);
  }
  public clear() {
    this.settingsProvider.clear();
  }
  
  public logStorageInfo() {
      console.log(this.get('website_host'));
      console.log(this.get('website_port'));
  }
  
  saveSettings(): void{
      this.set('website_host', this.website_host);
      this.set('website_port', this.website_port);
  }
}

I know I could just something like the following in the SetttingsPage constructor, but I’m trying to keep the Page/Provider logic as separated as possible:

this.get('website_host').then((val) => {
    this.website_host = val;
});

As I say, very new to Ionic so thanks in advance for any help, and of course any advice is appreciated! :slight_smile:

The Ionic Storage plugin might not yet be loaded if it’s using a Cordova plugin on your device! I would try to wrap the first call to the Storage inside a platform ready promise so you can be sure it’s there.

Does that fix the problem?

Hi

please check the examples on https://ionicframework.com/docs/storage/

You are pretty much not following it at all, in terms of setting/getting.

And you don’t need to create new Promise, as the getters/setters of Storage is returning promises on its own.

Regards

Tom

Thanks for your replies, I’ll look into platform ready.

Regarding setting/getting, I’m not sure what I’m not following? I’m getting the website_host from storage, then setting the corresponding value of my AppSettings with the value from get. I’ll go remake these functions without creating a new promise, at the time I posted this my mind was pretty frazzled so a fresh look at it should help.

Thank you both. :slight_smile:

I’ve made some progress. I can get the settings to load as expected when I use to separate get statements for website_host and website_port, but inside a for loop it doesn’t work. Here is the function in question:

getSettings(): AppSettings{
      if(!this.settings){
          this.settings = new AppSettings();
      }
      
      this.storage.ready().then(() => {
          //This works
          this.storage.get('website_host').then((val) =>{
              this.settings['website_host'] = val;
          })
          this.storage.get('website_port').then((val) =>{
              this.settings['website_port'] = val;
          })
          
          //This doesn't work
          for (var settingName in this.settings){
              this.storage.get(settingName).then((val) =>{
                  this.settings[settingName] = val;
              })
          }
          
          //This doesn't work either
          for (var settingName in ['website_host','website_port']){
              this.storage.get(settingName).then((val) =>{
                  this.settings[settingName] = val;
              })
          }
      })
      
      return this.settings;
  }

I appreciate this is probably isn’t something to do with Ionic itself, anyway will update this topic if I come across the solution.

@PeterC10 This isn’t an Ionic issue - the reason this block doesn’t work is because you’re missing a basic part about promises - they don’t complete execution by the time your function exits. In this for loop, each of those get calls returns a promise - you are abandoning all the promises it returns.

More importantly, the ROOT promise of this function: this.storage.ready().then(=> - also returns immediately. Effectively, the REAL execution of this function looks like:

  1. Create new app settings if null
  2. Get promise for when storage is eventually ready
  3. Return empty settings.

You haven’t made your code do anything else.

This means - you are not waiting for those promises to finish BEFORE you return this.settings - that value will update “sometime later”.

An important thing to understand is: Even if these promises execute very, very quickly (before the for loop completes) you will STILL not capture the values (in most cases). If you do it will be a race condition.

This is all a long way of saying that what you need is

  1. Your getter needs to return a promise that resolves with this.settings
  2. You need to add all of the get promises to an array and await all of them:
const promises = [];
for(...) {
 promises.push(this.storage.get(....))
}

return Promise.all(promises);
  1. Change this.storage.ready().then(() => to: return this.storage.ready()...

Yes, ultimately this means you won’t be able to use getSettings() synchronously but that’s because the storage object is itself fundamentally asynchronous. There is ONLY ONE WAY to turn this into “synchronous” code: using the await keyword, both inside this function, and in any function that calls it.

This is a problem I have spent quite a bit of time battling with. Apparently this has never been done before, but I actually created a decorator function that hides all of this complexity and simply allows you to treat a class member variable as a synchronous, available object (with the caveat that you have to wait for an event before you know you can use it reliably but I wrap this into my app.component.ts file and have it not even show the main app until that happens - usually takes a few milliseconds, nothing more). For a sample of the API that this creates, take a look at ngx-store, which heavily influenced this approach. My decorator’s functionality is identical, except for Ionic’s asynchronous native storage.

I am in the process of cleaning this code to publish it on NPM, but until then you can make the changes I suggested above.

1 Like

Peter did you get solution. ? I am looking to set 5 preferences and get it for later processing. How it can done ? I am looking a working sample

Anes