Exploring the UI

When you run the Freemium app, you see the main screen of the app. The app has two options: play a game or purchase goods in the store. The UI of this app is created as a NavigationPane. The two options are custom Page components that are pushed onto the NavigationPane when the user taps a button on the main page. The UI also has a Banner ad and an ImageButton to invite a friend to download the app over BBM.

Let's take a closer look at the main.qml file that's used to display the UI:

Screen showing the main UI of the Freemium sample app on a device.

import bb.cascades 1.2
import bb.platform 1.2

NavigationPane {
    id: navigationPane

    // These three Boolean properties represent 
    // the purchase state of the goods.
    property bool removeAdsPurchased: false
    property bool tbeamPurchased: false
    property bool freakinLaserBeamPurchased: false

    attachedObjects: [
        PaymentManager {
            id: rootPaymentManager
            onExistingPurchasesFinished: {
                if (reply.errorCode == 0) {
                   for (var i = 0; i < reply.purchases.length; ++ i){
                      console.log(
                        reply.purchases[i].receipt["digitalGoodSku"]);
                      PurchaseStore.storePurchase(
                        reply.purchases[i].receipt["digitalGoodSku"]);
                   }
                } else {
                    console.log("Error: " + reply.errorText);
                }
            }
        },
        // StorePage is where users will see all Digital Goods
        // for sale and be able to make purchases.
        StorePage {
            id: store
        },
        // The GamePlay page is a placeholder for when 
        // actual gameplay gets added to the app.
        GamePlay {
            id: game
        }
    ]
    Page {

        // The UI ... 
    
    }    
    onCreationCompleted: {
        rootPaymentManager.setConnectionMode(0);
        // As soon as the page loads, let's retrieve any 
        // past purchases from the local store and cache.
        PurchaseStore.retrieveLocalPurchases();
        rootPaymentManager.requestExistingPurchases(false);
    }
}

In the code sample above, the NavigationPane is used as the main component of the UI to stack the screens of the UI and show only one screen at a time. For more information about this type of navigation, see Drill down. This app has three digital goods and tracks whether the user has purchased them with three Boolean values:

    // These three Boolean properties represent 
    // the purchase state of the goods.
    property bool removeAdsPurchased: false
    property bool tbeamPurchased: false
    property bool freakinLaserBeamPurchased: false

There are two digital goods that represent items for game play, and there is one digital good that allows the user to remove ads from this app. This digital good demonstrates two monetization techniques: offering ads to encourage the user to purchase other apps and offering the ability to remove ads at a cost to the user.

The code sample above uses the attachedObjects property to attach the two custom pages StorePage and GamePlay to the NavigationPane. A PaymentManager instance is also attached to handle all requests to the Payment Service.

As soon as the UI is created, setConnectionMode() is called to make sure that we make only simulated purchases while we're debugging our app. Then the sample code requests all the user's existing purchases. We create a retrieveLocalPurchases() function that retrieves existing purchases from a local persistent store. A QSettings object is used to store and retrieve QString values representing the SKUs of purchased digital goods. We explore this function later.

The requestExistingPurchases() function requests the list of previous purchases from the Payment Service cache. We supply false as the argument so that the list of purchases is returned from the cache and not from the Payment Service server.

In this sample, requestExistingPurchases(false) returns goods that are purchased in the same app session, which is useful to know when you're testing the logic for restoring past purchases. Since this app is in test mode, requestExistingPurchases() doesn't return anything.

The actual layout of the UI is created as a Page with a Container, which is laid out in landscape orientation.

The orientation of the Freemium sample app is set in the bar-descriptor.xml file. For more information about running an application in a specific orientation, see Orientation.

The UI in the main Page has some key elements. The background image is added as an ImageView and a Banner appears on top of the background image. The Banner ad uses a custom FreemiumBanner component. You can learn more about this custom component in Adding ads.

    
    Page {
        id: main
        property double scale: 2.0
        Container {
            layout: DockLayout {
            }

            // Display the background image
            ImageView {
                scalingMethod: ScalingMethod.AspectFill
                horizontalAlignment: HorizontalAlignment.Fill
                verticalAlignment: VerticalAlignment.Fill
                imageSource: "asset:///images/backgroundLandscape.png"
            }
            // Display a banner ad. This control disappears
            // if the user purchases a specific digital good.
            FreemiumBanner {
                scaleX: main.scale
                scaleY: main.scale
                hideAd: removeAdsPurchased
                horizontalAlignment: HorizontalAlignment.Center
            }
        }
    }

Three more ImageView controls provide a visual representation of the state of the game based on the items that the user purchases. When the user purchases an item, the UI updates to display the new addition to the game.

            ImageView {
                imageSource: "asset:///images/tbeam.png"
                visible: tbeamPurchased
                scaleX: main.scale
                scaleY: main.scale
                horizontalAlignment: HorizontalAlignment.Center
                verticalAlignment: VerticalAlignment.Center
                translationY: 130
            }
            ImageView {
                imageSource: "asset:///images/freakinLaserBeam.png"
                visible: freakinLaserBeamPurchased
                scaleX: main.scale
                scaleY: main.scale
                horizontalAlignment: HorizontalAlignment.Center
                verticalAlignment: VerticalAlignment.Center
                translationY: 110
                translationX: -2
            }
            ImageView {
                imageSource: "asset:///images/ufo.png"
                scaleX: main.scale
                scaleY: main.scale
                horizontalAlignment: HorizontalAlignment.Center
                verticalAlignment: VerticalAlignment.Center
                translationY: -140
            }

The two options in the game ("Shop" and "Play") are added to a Container as ImageButton objects. The app uses the push() function to push the two custom pages StorePage and GamePlay to the NavigationPane when the clicked() signal is emitted.

            Container {
                horizontalAlignment: HorizontalAlignment.Center
                verticalAlignment: VerticalAlignment.Bottom
                bottomPadding: 100
                layout: StackLayout {
                    orientation: LayoutOrientation.LeftToRight
                }
                ImageButton {
                    defaultImageSource: "asset:///images/play.png"
                    onClicked: navigationPane.push(game)
                }
                ImageButton {
                    defaultImageSource: "asset:///images/shop.png"
                    onClicked: navigationPane.push(store)
                }
            }

Finally, a Container with an ImageButton is added to the UI so that the user can invite a friend to download this app over BBM. The inviteUserToDownloadViaBBM() function is a public slot that is declared in the header file of our sample app and exposed to QML so that it can be called when the clicked() signal is emitted. This walkthrough doesn't go into the details of this technique but you can learn more about exposing C++ values and objects to QML in QML and C++ integration. To learn more about BBM, see BBM Social Platform.

            Container {
                horizontalAlignment: HorizontalAlignment.Right
                verticalAlignment: VerticalAlignment.Bottom
                rightPadding: 25
                bottomPadding: 25
                ImageButton {
                    defaultImageSource: "asset:///images/ic_bbm.png"
                    onClicked: {
                        mainApp.inviteUserToDownloadViaBBM();
                    }
                }
            }

To view the class definition that's used to declare the public functions for adding BBM support to your app, see the following code samples:

#ifndef FreemiumSampleApp_HPP_
#define FreemiumSampleApp_HPP_

#include <QObject>
#include <bb/platform/bbm/Context>
#include <bb/platform/bbm/MessageService>

#include "PurchaseStore.hpp"

namespace bb {
namespace cascades {
class Application;
}
}

/*!
 * @brief Application pane object
 *
 * Use this object to create and init app UI, 
 * to create context objects, to register 
 * the new meta types etc.
 */
class FreemiumSampleApp: public QObject {
Q_OBJECT
public:
	FreemiumSampleApp(bb::cascades::Application *app);
	virtual ~FreemiumSampleApp() {
	}

public Q_SLOTS:
	void registrationStateUpdated(
			bb::platform::bbm::RegistrationState::Type state);

	//Expose this function so it can be called from QML
	Q_INVOKABLE
	void inviteUserToDownloadViaBBM();

private:
	//All fields here used for BBM invitation support
	bb::platform::bbm::Context *m_context;
	bb::platform::bbm::MessageService *m_messageService;
	PurchaseStore *m_purchaseStore;
};

#endif /* FreemiumSampleApp_HPP_ */
#include "FreemiumSampleApp.hpp"

#include <qt4/QtCore/QUuid>

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

using namespace bb::cascades;

FreemiumSampleApp::FreemiumSampleApp(bb::cascades::Application *app) :
		QObject(app), m_purchaseStore(new PurchaseStore(this)) {

	QmlDocument::defaultDeclarativeEngine()->rootContext()->setContextProperty(
			"PurchaseStore", m_purchaseStore);

	//Expose this class to QML using the handle "mainApp", this will let our QML call
	// the code needed to use BBM Invites
	QmlDocument::defaultDeclarativeEngine()->rootContext()->setContextProperty(
			"mainApp", this);

	// create scene document from main.qml asset
	// set parent to created document to ensure it exists for the whole application lifetime
	QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);

	// create root object for the UI
	AbstractPane *root = qml->createRootObject<AbstractPane>();
	// set created root object as a scene
	app->setScene(root);

	// All code below here allows the app to invite users via BBM to download the app
	m_context = new bb::platform::bbm::Context(
			QUuid("bf38c081-c104-4660-a958-618392541a24"), this); //UUID was randomly generated for dev testing
	if (m_context->registrationState()
			!= bb::platform::bbm::RegistrationState::Allowed) {
		connect(m_context, SIGNAL(
				registrationStateUpdated(
						bb::platform::bbm::RegistrationState::Type)), this,
				SLOT(
						registrationStateUpdated(
								bb::platform::bbm::RegistrationState::Type)));
		m_context->requestRegisterApplication();
	}
}

// This method is exposed to QML so we can initiate a BBM download
// invite without switching contexts
void FreemiumSampleApp::inviteUserToDownloadViaBBM() {
	if (m_context->registrationState()
			== bb::platform::bbm::RegistrationState::Allowed) {
		m_messageService->sendDownloadInvitation();
	}
}

void FreemiumSampleApp::registrationStateUpdated(
		bb::platform::bbm::RegistrationState::Type state) {
	if (state == bb::platform::bbm::RegistrationState::Allowed) {
		m_messageService = new bb::platform::bbm::MessageService(m_context,
				this);
	} else if (state == bb::platform::bbm::RegistrationState::Unregistered) {
		m_context->requestRegisterApplication();
	}
}

Last modified: 2013-12-21



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus