ScopedValueSaver - never worry about App State again


#1

Howdy. I’ve been working on an iOS release of my project and one of the things I learned the hard way that iOS likes to do is not actually quit your application and call any of your destructors! In my case, my desktop project used all of the destructors to save data to disk. So, I created a wrapper class that I use to wrap all of my state-like variables which automatically saves them to disk whenever they’re updated. And if it’s the first time creating the variable, it’ll automatically be added to the properties file. Oh, and it’ll automatically create the properties file too. Yeah, I didn’t want to ever have to think about any of that state-related stuff ever again. Oh, and the wrapper acts like the original type, so no need to worry about having to convert.

It works with primitives and complex objects. It’s built around the Value class and is pretty simple to use. Check out the example project which has a little Color widget that changes color when you click on it. When you change the color, the settings file is automatically updated and restores its value the next time you run the application. When you click the button to open the Colour Selector, the ColorSelector’s initial color is that of the widget, and when you modify the ColourSelector, the widget’s color changes. Also, when you click the widget, the ColourSelector’s color changes. So, this demonstrates how to link one widget to another so they reflect the same internal value.

you can check it out here:

Hopefully anyone that needs to store/restore their App’s state constantly and doesn’t want to write something to deal with an ApplicationProperties object will find this useful!


Load/save last state for iOS
#2

just wanted to add how this is useful.
so, in your standard project that you need to save/load settings from, you might have this:

struct Foo
{
private:
    ApplicationProperties props;
    void initProperties();
    void loadParams();
    void saveParams();

    int paramA{0};
    float paramB{0.f};
    bool paramC{false};

implemented thusly:

void Foo::initProperties()
{
    PropertiesFile::Options options;
    options.applicationName = ProjectInfo::projectName;
    options.filenameSuffix = ".settings";
    options.osxLibrarySubFolder = "Application Support";
    options.folderName = String(ProjectInfo::companyName) + File::separatorString + String(ProjectInfo::projectName);
    options.storageFormat = PropertiesFile::storeAsXML;
        
    props.setStorageParameters(options);
}

loading is accomplished like this:

void Foo::loadParams()
{
    String pA = props.getProperties().getUserSettings()->getValue("paramA",
                                                                  "0");
    var tempVar = VariantConverter<String>::toVar(pA);
    paramA = VariantConverter<int>::fromVar( tempVar );

    String pB = props.getProperties().getUserSettings()->getValue("paramB",
                                                                  "0");
    tempVar = VariantConverter<String>::toVar(pB);
    paramB = VariantConverter<float>::fromVar( tempVar );

    String pC = props.getProperties().getUserSettings()->getValue("paramC",
                                                                  "0");
    tempVar = VariantConverter<String>::toVar(pC);
    paramC = VariantConverter<bool>::fromVar( tempVar );    
}

and saving is accomplished like this:

void Foo::saveParams()
{
    var pA(paramA);
    props.getProperties().getUserSettings()->setValue("paramA",
                                                      pA);
    var pB(paramB);
    props.getProperties().getUserSettings()->setValue("paramB",
                                                      pB);
    var pC(paramC);
    props.getProperties().getUserSettings()->setValue("paramC",
                                                      pC);
}

very boring, boilerplate-ish code that’s tedious to implement, and easy to forget to include a parameter in your saved settings.

now, because those params are private members, if you need to access them or change them, you gotta add getters/setters to your class. and if you want something to happen when you modify them, you gotta do it manually:

void setParamA( int i ) { paramA = i; doSomethingWithA(); }
int getParamA() { return paramA; }

void setParamB( float f ) { paramB = f; doSomethingWithB(); }
float getParamB() { return paramB; }

void setParamC( bool b ) { paramC = b; doSomethingWithC(); }
bool getParamC() { return paramC; }

Accessing them from an outside class also sucks:

struct Bar
{
    Bar(Foo& foo_) : i( foo_.getParamA() ), foo( foo_ ) { }
    void update() { if( foo.getParamC() ) { doSomethingAmazing(); }
private:
    int& i;
    Foo& foo;
};

So, this is all you do with ScopedValueSaver:

struct Foo
{
    Foo() {}
    ~Foo() {}
    auto& getParamA() { return paramA; }
    auto& getParamC() { return paramC; }
private:
    ScopedValueSaver<int> paramA{"paramA", 0, [this](Value&) { doSomethingWithA(); } }
    ScopedValueSaver<float> paramB{"paramB", 0.f, [this](Value&) { doSomethingWithB(); } }
    ScopedValueSaver<float> paramC{"paramC", false, [this](Value&) { doSomethingWithC(); } }
};

struct Bar
{
    Bar(Foo& f) : 
        svsBool(f.getParamC(), 
                "svsBool", 
                [this](Value& v) { if( v == svsBool ) { this->doSomethingAmazing(); } ) 
    { 
    }
    ScopedValueSaver<bool> svsBool;
    void doSomethingAmazing() 
    { 
        DBG( "amazing" ); 
        svsBool = true;  //Foo::paramC is now true
    }
};

and, when you quit the app, paramC is saved as true. the next time you run it, paramC is restored as true, even though it was initialized the first time the program was run as false.