Secrets of the BlackBerry Jam Asia Conference App, Part 1: Implementing an End-User License Agreement

Cascades

BlackBerry Jam Asia App

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.

EULA Flowchart

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:

EULA Sample Text

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:

EULA License Dialog Box

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:

EULA File

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:

EULA Project Structure

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!

About Donny Halliwell

I'm the Editor-in-Chief for the Inside BlackBerry blogs. There's no disputing, #TeamBlackBerry is awesome. I'm pumped to have the opportunity to talk with you and bring you the latest and greatest from Inside BlackBerry every day. In my spare time, I can be found helping entrepreneurs and startup companies grow their business. I spend the rest, obsessing over the auto industry, or enjoying music. Follow me on Twitter or Google+

Join the conversation

Show comments Hide comments
+ -
blog comments powered by Disqus