Guest post by Ekkehard Gentz, BlackBerry Elite Developer
This is the first post in a series about how we created the BlackBerry Jam Asia app. In today’s post, I’ll show you how to implement an end-user license agreement (EULA) in an app. In most cases, developers should provide an EULA, which has the user has to agree to app conditions before using it, whether the app is free or paid.
BlackBerry Jam Asia 2013 Conference APP
This year I developed the official BlackBerry Conference App for BlackBerry Jam Asia 2013 in Hong Kong. The BlackBerry Jam Asia 2013 app is a native BlackBerry 10 app developed with Cascades/QML/C++, and can be downloaded from BlackBerry World for free. I recommend downloading the app so you can see how the EULA works in the context of a complex app.
The EULA is the first Dialog visible after downloading and opening the App, but it was the last part I developed. I had implemented a hard-coded English version of the EULA, but the license should be available in a variety of languages.
UI (QML)
Let’s take a look at the UI first. We need a dialog with a title, the license text and two buttons: “Agree” and “Don’t Agree.” To do this, we created a custom dialog with alias properties to fill the fields and a result string.
Dialog { id: eulaDialog property string result property alias title: titleLabel.text property alias body: bodyLabel.text property alias button1: acceptButton.text property alias button2: cancelButton.text
The dialog gets a container with a solid background color so nothing will shine through. The color depends on the theme:
Container { background: isDark() ? Color.Black : Color.White horizontalAlignment: HorizontalAlignment.Fill verticalAlignment: VerticalAlignment.Fill
To detect the theme we use a function:
function isDark() { return Application.themeSupport.theme.colorTheme.style == VisualStyle.Dark }
Inside the dialog container, we have a container for the title and another container for the content. The title container in the sample app has a blue background; the conference app uses the special JAM color.
The content is placed inside a ScrollView, so it doesn’t matter how long your license text is, or if it’s running on a 720×720 Q10 or 720×1280 Z30.
The accept and cancel buttons are placed below the content. Hitting one of them sets the result string and closes the dialog. Here’s the accept button:
Button { id: acceptButton text: qsTr("I Agree") layoutProperties: StackLayoutProperties { spaceQuota: 1 } focusPolicy: FocusPolicy.KeyAndTouch onClicked: { eulaDialog.result = "ACCEPTED" eulaDialog.close() } }
Complete source code of this custom dialog can be found here:
assets/EULADialog.qml
The logic to show the dialog or not is here:
assets/main.qml
This is a TabbedPane in ConferenceApp and a simple Page in the sample app. At first we have to add the Custom Dialog as an attachedObject:
attachedObjects: [ EULADialog { id: eulaDialog property bool firstRun: true onClosed: { if (eulaDialog.result == "ACCEPTED") { app.setEulaAccepted() // do your normal startup stuff now return } if (firstRun) { noEulaDialog.exec() return } Application.requestExit(); } }, SystemDialog { id: noEulaDialog title: "EULA License" body: qsTr("You must accept the EULA in order to use the App.") + Retranslate.onLanguageChanged confirmButton.label: qsTr("OK") + Retranslate.onLanguageChanged confirmButton.enabled: true cancelButton.enabled: false onFinished: { eulaDialog.firstRun = false eula() } }, ..... more ]
As you can see, there’s a second dialog: a SystemDialog. This dialog is used if the user does not accept the license. There’s only one chance for the user to retry; if the license is not accepted the second time, the app is closed:
Application.requestExit();
If the EULA is accepted, we call a method from C++:
app.setEulaAccepted()
If the EULA is accepted, a value was inserted into settings, so that the EULA is not displayed the next time the user accesses the app.
Now let’s take a look at how to open the EULA dialog. At the bottom of main.qml as part of the onCreationCompleted slot:
onCreationCompleted: { if (app.showEula()) { eulaTimer.start() } else { // do your normal startup stuff now doItAgin.visible = true } }
We ask the C++ application if the EULA dialog must be opened:
app.showEula()
While testing the BlackBerry Jam Asia app on different devices and operation systems, we found out that an older 10.1 OS Version had some problems opening the dialog directly from the onCreationCompleted{}. So we did an async and started a single-shot QTimer, which then opens the dialog.
This QTimer was also attached as an object:
attachedObjects: [ ....... QTimer { id: eulaTimer interval: 500 singleShot: true onTimeout: { eula() } } ]
onTimeout calls a function:
function eula() { var data = app.eulaContent() eulaDialog.title = data.title eulaDialog.body = data.body eulaDialog.button1 = data.button1 eulaDialog.button2 = data.button2 // now it's safe to open the Dialog eulaDialog.open() }
We get the localized content from C++:
app.eulaContent()
The content is a QVariantMap, so can directly be used as a Javascript Object and we set the values of alias properties. Finally, when starting the app for the first time we see the custom dialog:
Above you see a localized dialog with a German title and button text. Starting the Conference App you’ll get a real EULA License text.
If the user doesn’t agree, this SystemDialog appears exactly one time and opens the EULA dialog again:
Business Logic (C++)
Now let’s see what happens at C++ side. Inside the constructor of applicationUi.cpp the QTimer must be registered as type, so QML knows it:
qmlRegisterType<QTimer>("my.library", 1, 0, "QTimer");
also we need the ‘app’ context property:
qml->setContextProperty("app", this);
Setting the context property isn’t enough – we must tell Qt that some of our methods can be invoked. This is done in the applicationUi.hpp Headerfile:
Q_INVOKABLE bool showEula(); Q_INVOKABLE QVariant eulaContent(); Q_INVOKABLE void setEulaAccepted();
Back to the .cpp file and take a deeper look at these methods:
bool ApplicationUI::showEula() { QSettings settings; if (settings.value(SETTINGS_KEY_EULA_ACCEPTED).isNull()) { return true; } return false; }
showEula() uses QSettings to see if the EULA was already opened. QSettings is a simple way to persist values in a local secure filestore.
You’ll find the settings file from TargetFileSystemNavigator – View in your Momentics IDE inside the sandbox of your app:
This is the path to the settings file:
data/Settings/<your-vendor-name>/<your-app-name>.conf
Opening the settings file you’ll find this entry if the EULA is accepted:
[General] eula_read=true
Here’s how to set the value:
void ApplicationUI::setEulaAccepted() { QSettings settings; bool accepted = true; settings.setValue(SETTINGS_KEY_EULA_ACCEPTED, QVariant(accepted)); }
Hint: if you want to test again if the EULA Dialog will be displayed, delete the Settings File from TargetFileSystemNavigator.
Now the last missing piece is getting the localized EULA. Here’s the project structure from sample app:
All EULA texts are contained inside a JSON file:
assets/app_data/eula.json
The structure of this JSON is easy to understand:
[ { "locale":"pl", "title":"AKCEPTACJA LICENCJI:", "body":"………… Lorem", "button1":"Zgadzam się", "button2":"Nie zgadzam się" } ,... ]
A JSON Array contains JSON Objects, where each JSON Object has as index the “locale” property (‘en’, ‘de’, ‘pl’,…) and properties for “title”, “body”, “button1″ and “button2.″
Working with JSON in Cascades Apps is really easy. Here’s the code on how to read this JSON Array from assets/app_data into a QVariantList:
QVariantList ApplicationUI::readEulaFromJson() { JsonDataAccess jda; QVariantList eulaList; QString eulaFilePath; eulaFilePath = QDir::currentPath() + "/app/native/assets/app_data/eula.json"; if (!eulaFile.exists()) { qDebug() << "no eulaFile file found in assets - using english"; return eulaList; } bool ok = eulaFile.open(QIODevice::ReadOnly); if (ok) { eulaList = jda.loadFromBuffer(eulaFile.readAll()).toList(); eulaFile.close(); } else { qDebug() << "cannot read eulaFile file: " << eulaFilePath; } return eulaList; }
As soon as you get the list, you can search for the current locale. If no entry is found, check for the first 2 characters only, which is the language. For example, ‘de_DE’ and ‘de_AT’ are valid locales for german language in Germany (DE) and Austria (AT), so if ‘de_DE’ is not found, we look for ‘de’. If again no entry is found, we use ‘en’ – english as default.
See the details in applicationUi.cpp:
QVariant ApplicationUI::eulaContent() { ... } QVariantMap ApplicationUI::euladoc(const QString& locale) { ... }
Don’t forget to import the libraries in QML
import bb.system 1.0 import my.library 1.0
Also don’t forget to add the libraries into your .pro file_
LIBS += -lbbsystem -lbb -lbbdata
Summary
From this sample you have learned how to:
- Use QSettings to persist values
- Access JSON data files
- Communicate between C++ and QML
- Use QTimer in QML
- Write a custom dialog in QML
- Use a SystemDialog in QML
Download and Discuss
The Sample APP is available at GitHub Open Source (Apache 2 License).
I also created a thread in the forums. Have fun with the sample app, and copy/paste what you need to implement an EULA Dialog into your own apps!