The Persistence of Cascades Memory

BlackBerry 10 Cascades Persistent Memory

Last blog I did a write up on Sheets and mentioned they would make a good use for a “Settings” page.  So the next logical blog seemed to be one about making a Settings page, and to do so there are a few things it probably should have. First, there’s a good chance you may not write the Sheet in the same QML file as the elements you want to control. Creating an alias will allow a QML file to expose its element id’s to other QML files.  Second, the settings should be persistent, meaning when the user selects options they want, closes the app, and then open the app again the options they selected are preserved.

Exposing QML element id’s to other QML files:

The reason I say there’s a good chance your your Sheet won’t be in the same QML file is imagine this scenario… You create an app with multiple tabs.  To keep the main.qml from containing all of the tab’s code, create new files for each tab.  Then you’d have something like:

import bb.cascades 1.0

TabbedPane {   
    showTabsOnActionBar: true
    Tab {
        title: qsTr("Tab 1")
        TabOne {
            id: maintab1
        }
    }
    Tab {
        title: qsTr("Tab 2")
        TabTwo {
            id: maintab2
        }
    }
    Tab {
        title: qsTr("Tab 3")
        TabThree {
            id: maintab3
        }
    }   
}

Where “TabOne” calls the QML file TabOne.qml as long as it is in the assets folder within your project.  The same goes for TabTwo and TabThree.  And even though I named the files TabX, you can name them anything you want, so long as the first letter is capitalized.

No other code is needed in your project to expose full QML files to each other.  However if you want to call elements from within that file that takes a bit more of coding. This is easiest explained using my “Settings” sheet example.  In my main.qml page I have set up a Sheet, inside of that sheet I have a button that I want to control a label on tab 3.  First create the contents of tab3 with label.  Then expose that’s label’s id with an alias.  Creating an alias is how it will be able to be called by another QML file.  To do that you do the following:

import bb.cascades 1.0

Page {
    property alias tab3title: tab3title
    Container {
...
        Label {
            id: tab3title
            text: "Tab 3 Title"
           
        }
...
    }
}

The creation of the alias is the line: “property alias tab3title: tab3title” which means anytime you call tab3title in another QML file it will call tab3title in from this QML file.  You do not have to call them the same but I find it easiest/less confusing to do it this way.  If you don’t want to call them the same the format is:

property alias <whatever you want the alias to be> : <the element you are calling>

Now on the main.qml side of it you would have the following:

import bb.cascades 1.0

TabbedPane {
    attachedObjects: [
        Sheet {
            id: mysheet
            Page {
                Container {
                    Button {
                        id: button1
                        onClicked: {
                            text: "Change Tab 3 Title"
                             maintab3.tab3title.text = "New Tab 3 Title"
                        }
                    }
                }
            }
        }
    ]
    ...
    }
    Tab {
        title: qsTr("Tab 3")
        TabThree {
            id: maintab3
        }
    }
}

Where when you call the Sheet and press button1 it will send the signal

maintab3.tab3title.text = "New Tab 3 Title"

which looks to maintab3 which is the id of TabThree (the other QML file we have called in) and then inside of maintab3 it looks for tab3title then the property in which it will change is the text.  After the equal sign is what that property (text) will be changed to.  In this case we are changing the text from “Tab 3 Title” to “New Tab 3 Title”.  This same method can be applied to any element.

Persistent Memory:

I will preface this section that the 2 C++ functions I am using, I have taken from RIM’s StarShip Settings example (https://github.com/blackberry/Cascades-Samples/tree/master/starshipsettings).  So for additional info on this part you may refer there as well.  I will, however, try to go a bit more in depth with my exexplanation than what you see in that example.

Below is a full copy of my <projectname>.cpp file (in my example I have named my project “TabTesting3”… I don’t really know why I named it something that silly).

#include "TabTesting3.hpp"

#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/AbstractPane>
#include <QSettings>

using namespace bb::cascades;

TabTesting3::TabTesting3(bb::cascades::Application *app)
: QObject(app)
{
    QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);
    qml->setContextProperty("app", this);

    AbstractPane *root = qml->createRootObject<AbstractPane>();

    app->setScene(root);
}

TabTesting3::~TabTesting3()
{
}

QString TabTesting3::getValueFor(const QString &objectName, const QString &defaultValue)
{
    QSettings settings;

    if (settings.value(objectName).isNull()) {
        return defaultValue;
    }

    return settings.value(objectName).toString();
}

void TabTesting3::saveValueFor(const QString &objectName, const QString &inputValue)
{
    QSettings settings;
    settings.setValue(objectName, QVariant(inputValue));
}

The big things to realize what is here is the #include <QSettings>, setting the context property as “app” (you can call this whatever you want but you’ll see when I call the functions into the QML file you call <context property>.<function name>), and the two functions getValueFor and saveValueFor.

Without getting too long winded, one thing I learned about C++ is it doesn’t matter what is going on in a function rather the important thing you need to know as a developer is what are the inputs and given those inputs what is the output.  Think of these functions as “black boxes”. Especially since I didn’t write these functions I’ll accept them as “black boxes” and only care about the input/output of the functions.

Therefore, I am not going to explain the function, rather just the input/output.  Making things easier, both functions have the same format of inputs.  The first input of each is the value you want to either “get the value for” or “save the value for” and the second input is the default value (if there is no value set for the “get” function or no value already saved for the “save” function). Now for the outputs, the “getValueFor” function what you will use where you would usually define a property. And the “saveValueFor” function stores a new/changed value and will be called on a signal after you change a property (so the new/change will be saved).  Using these two functions properly together they create a loop of saving a change, calling that saved change, saving another change, calling that change, etc…

This next part of the example builds on the previous part (using multiple QML pages) and now adds in persistent memory too.  We’ll add a toggle switch to the Sheet that changes the background color.  Take a look below the code for the main.qml and TabThree.qml.

main.qml:

import bb.cascades 1.0

TabbedPane {
    attachedObjects: [
        Sheet {
            id: mysheet
            Page {
                Container {
                    Label {
                        text: "Toggle Switch that controls background on Tab 3"
                        textStyle.fontSizeValue: 10.0
                        multiline: true
                    }
                    ToggleButton {
                        id: tog5
                        objectName: "tog5"
                        checked: app.getValueFor(objectName, "true")
                        onCheckedChanged: {
                            app.saveValueFor(tog5.objectName, checked)
                            if (checked == true) {
                                maintab3.maincontainer.background = Color.create(0.9, 0.9, 0.9);
                            } else {
                                maintab3.maincontainer.background = Color.create(0.7, 0.8, 0.9);
                            }
                        }
                    }
                }
            }
        }
    ]
    ...
    }
    Tab {
        title: qsTr("Tab 3")
        TabThree {
            id: maintab3
        }
    }
}

TabThree.qml:

import bb.cascades 1.0

Page {
    property alias maincontainer: maincontainer
    Container {
        id: maincontainer
        background: {
            var blah = app.getValueFor("tog5", "true")
            if (blah == true) {
                maincontainer.background = Color.create(0.9, 0.9, 0.9);
            } else {
                maincontainer.background = Color.create(0.7, 0.8, 0.9);
            }
        }
...
    }
}

The loop I talked about above can be seen in the main.qml file where tog5’s check property is decided by the output of the getValueFor function.  And inside of the onCheckChecked signal the saveValueFor function is run to save the new value of checked property. Then on the TabThree.qml side, the background is a Boolean statement based off the checked property of tog5. This method should work with all types of elements.

As a final recommendation is to download the StarShip Settings example and the full source code to this example, where I have some other features added in that have persistent memory too (https://github.com/bcs925/BrianScheirerOpenSource/tree/master/TabTesting3). Then once you have them start playing around with my formatting and try mimicking it for whatever element you want it for (DropDown, SegmentedControl, etc).  And as always feel free to ask questions in the comments below.

-Brian