How to Port From Desktop to Mobile

From Qt Wiki
Jump to navigation Jump to search


How to Migrate Your Application from the Desktop to a Mobile Platform
This tutorial originally appeared on qtlabs.org.br. and was written by a team of developers from INdT.

Introduction

Developing for mobile devices require that you pay attention to some details, and migrating your destkop application to the mobile world can be hard if you don't use the right tools and framework. Thanks to Qt and QML, this job can be easy and won't demand too much time from the developer.

This tutorial aims at helping the developer that wants to migrate his desktop application to the mobile, giving tips about architectural changes in your application and mobile best practices.

About QML

We will use a desktop application as an example during this tutorial and we will guide you during the migration of this example to a mobile version. We'll use QML, the mark up language for Qt Quick, to build the mobile interface.

QML provides the means for building dynamic, rich and custom user interfaces. It also improves the integration between JavaScript and Qt's Object system, allowing it to easily integrate with your C++ code.

About Qt Mobility

Qt Mobility is a set of Qt APIs with features that are usually present on mobile devices, specially phones. Besides being APIs that aim the mobile world, they support different backends, making it easy to develop applications for phones in the desktop. This avoid the "round-trips" between developing on desktop and testing on the phone, allowing the application's development to be faster.

Examples of these APIs are: bearer management, contacts, location, multimedia and sytem information. Basically, Qt Mobility will give you access to the phone features and its sensors.

Canvas-based UIs

While developing desktop applications using Qt the developer usually will use QWidgets, which are widgets developed with the desktop in mind and that will look exactly like the "native" widgets of the platform. This also works for the mobile world but today it is more usual to find UIs that are rich and fluid.

In order to properly have UIs that can be animated and that doesn't look like the platform's widgets, one should use a canvas based UI. Qt provides a canvas for developers called QGraphicsView. One of the many differences between QGraphicsView's interfaces and QWidget's ones is that QWidget depends on the screen resolution and that if the developer chooses to use a canvas he must be aware that most of the common widgets are not yeat available out of the box, but that they can be easily built using pixmaps.

This gives extra freedom to the application's designer as he can create all the elements of the screen and they will look like exactly the way they were designed. It is important to say that QML runs on top of QGraphicsView and that it eases the development of the user interfaces. However it is still possible to make everything in plain C+.

This tutorial will focus in the use of QML and its integration with C, trying to keep all the logic in C+ and just making your UI using QML. This is the easiest path if the developer needs to maintain a mobile version of an application at the same time that he maintains the desktop version.

Considerations about mobile UIs

You need to take special care related to the areas that will be "clickable" in your application when you develop to a mobile device.

The "clickable" areas of your application should be big enough to handle the "average" finger size of someone and also you should not forget that the fingers used to interact with your application may vary depending on the device's orientation (landscape vs portrait).

Be sure to make the areas small enough to not overlap each other. Also avoid the device's corners as they are harder for the user to select. During design time, make sure you question yourself if the user is going to use your application with just one hand for example as this may change the user experience of your application.

Screen Resolution

When developing mobile applications, one thing to keep in mind and take care of is that: If you want your application to run on a wide range of devices, you have to cope with different screen resolutions, that have different properties and features. It is important to allow your application to be scalable to give the user the best experience possible on the specific device. Additionally, many devices support screen rotation when your application is running, so be prepared to dot it.

When writing mobile applications, the developer must avoid using fixed size for items placed on the screen. Qt and QML provide ways to layout items that help you to deal with that. Take a look at the example below.

/* main.qml */
Item {
 width: parent.width
 height: parent.height

 Rectangle {
 id: rect
 width: 100
 height: 100
 color: "red"

 anchors.centerIn: parent

 MouseArea {
 anchors.fill: parent
 onClicked: rect.color == "red" ? rect.color = "blue" : rect.color = "red"
 }
 }
}

The example above makes usage of anchors, in order to adapt the application size to screen resolution of the device. Additionally, is important to make your application starts on fullscreen, so the QML item above will anchor at QDeclarativeView, like the example below:

/''' main.cpp */
QDeclarativeView window;
window.showFullScreen();
window.setSource(QUrl::fromLocalFile("main.qml"));

Memory Management

When dealing with mobile development, the developer must be aware that resources are very limited, e.g. CPU power and memory. So, when porting your desktop application to mobile world, memory management has to be seriously taken into account, since it can become a bottleneck on application execution.

Memory Allocation

There are two ways that a user can allocate memory: Stack allocation and Heap allocation.

Every time a program starts its memory is organized in the following segments:

Text segment: The place where the program code is placed (the machine instructions)

  • Stack segment: Reserved block of memory that gets allocated when a thread/function is started, in order to handle local variables and parameters
  • Heap segment: Memory block that is reserved for dynamic allocation during the program execution

Heap

The Heap segment is a big block of free memory where user can allocate portions of memory dynamically. For instance, in C a user can allocate memory in the heap using the malloc operator, however in C++ you use the new operator to allocate memory in the heap. The precise location of the requested memory is not known in advance, so those dynamic allocator operators return a pointer to user, in order to manage the allocated memory. Additionally, it is worth telling that continuous block of memory requested does not assure that a sequential block of memory is going to be allocated, this is due to memory fragmentation resulted by various memory allocation and deallocation operations.

Stack

Whenever a thread is started, a block of memory is allocated and given to it, in order to store its local variables and parameters. So, for instance when a C program starts (the main thread) it receives a block of memory, the Stack. When a function is called, a block of memory of this stack (on top of it) is reserved for that function to store its data and gets pushed on the Stack.

Example

int main(int argc, char** argv)
{
 int a; // allocated on stack

int b[10]; //allocated on the stack

int '''p = (int''') malloc(sizeof(int)); // allocated on the heap

return 0;
}

In the example above variable //*a// and //b// (the whole array) get allocated on the stack. On the other hand, //p// gets dynamically allocated on the heap.

When the thread/function exits, all the memory that was being used by it (local variables, parameters, etc…) gets automatically deallocated whereas all memory that was dynamically allocated must be explicitly deallocated by the user, or leaks of memory may appear.

So, the code above would be better written like this:

int main(int argc, char'''* argv)
{
 int a; // allocated on stack

int b[10]; // allocated on stack

int '''p = (int''') malloc(sizeof(int)); // allocated on the heap

free(p); // free memory on heap

return 0;
}

Heap Allocation vs Stack Allocation

Memory allocation on the stack is faster when compared to the heap.

When a memory block is requested to the heap, by new or malloc for instance, the dynamic memory operator starts seeking for an available amount of memory that fits the requested size. On the other hand, the stack size needed for a function is known beforehand. When the function is called a piece of the stack is reserved for the function and then all local variables and parameters are placed there. Additionally, the memory of the stack tends to be used very frequently, due to several function calls, thus it tends to be mapped to the processor's cache, increasing cache hit ratio, which makes the access time shorter than the time spent to access some memory allocated at the heap, which you have higher probability to get a page fault.

Thus, it is preferable to use stack allocation as much as possible when developing something mobile. However, beware that the stack has limited size depending on the platform (Symbian for instance have 8KB by default). Although the stack can be extended, allocating huge amount of memory on stack can cause a stack overflow and makes your program crash. To put in a nutshell, the developer must use memory allocation wisely, balancing between stack and heap, in order to make an fast application.

Note on Symbian Platform

On Symbian you can explicitly set the size of both Stack and Heap sizes. This is done by using EPOCSTACKSIZE and EPOCHEAPSIZE statements at the .mmp file, or put on your .pro file of your Qt application, as described on the example below:

/* app.pro */
QT ''= core gui network

TARGET = myapp
TEMPLATE = app


SOURCES''= main.cpp

symbian {
 TARGET.UID3 = 0xeb22583b

 TARGET.EPOCSTACKSIZE = 0x14000 # Stack will have 80kB
 TARGET.EPOCHEAPSIZE = 0x020000 0x800000 # Heap minimum of 128kB and maximum of 8MB
}

Call by Value vs Call by Reference

Since mobile resources (CPU and memory) are very limited, unnecessary memory copies must be avoided by the developer. One important point to avoid such copies is to watch the way you pass parameters when calling functions. Calling functions by value makes a temporary copy of the object, that is being passed as parameter, in memory. This can take unnecessary space in memory and waste CPU cycles to create a copy of the object, when the same function call could be made by reference, saving that used memory. Thus, if a function call could be done by "call by reference" it is preferable to do it, in order to avoid unnecessary memory copy operations. C++ support call by reference by adding the & operator to the parameters' description of the function. C does not support call by reference, even when you create a function that receives a pointer that pointer is copied in memory, which is a great way to "simulate" call by reference in C.

Below are some examples:

class MyObject
{
public:

 int i;
};

struct myStruct {
 int i;
};

void add(struct myStruct''' s)
{
 s->i+'';
}

void add(MyObject &obj)
{
 obj.i;
}

int main(int argc, char** argv)
{
 MyObject o;
 o.i = 10;
 add(o); // called by reference

 struct myStruct '''my = (struct myStruct''') malloc(sizeof(struct myStruct));
 add(my); // pointer "my" is copied to the function add

 return 0;
}

Screen Orientation

Screen orientation is not supported before Qt 4.7.2. So some platform specific code is necessary to lock or unlock the device orientation if you are compiling with old versions of Qt. However, if you are using QtCreator to develop your applications, you don't need to worry about it, since it abstracts all the necessary code in a helper class.

Qt Creator Wizard

While creating a new project, you will be prompted to choose what kind of orientation behavior your application will have. The default option is "Automatically Rotate Orientation". If your application is designed to work in just one screen orientation, you can choose one of the other options, which are "Lock to Landscape Orientation" and "Lock to Portrait Orientation".

New Qt Quick Application

Platform-Specific Code

If you need to use platform specific code to set the device orientation, you will need to add some ifdefs in your code. You can follow QtCreator approach extending a QWidget class and adding an extra method to abstract orientation changes. For example, to handle screen orientation for Symbian and Maemo devices you can create a helper class like the following:

// …

#ifdef Q_OS_SYMBIAN
#include <eikenv.h>
#include <aknappui.h>
#endif

ApplicationView::ApplicationView(QWidget *parent)
 : QDeclarativeView(parent)
{

}

void ApplicationView::setOrientation(ScreenOrientation orientation)
{
#if defined(Q_OS_SYMBIAN)
 if (orientation == ScreenOrientationAuto)
 return;

 CAknAppUiBase::TAppUiOrientation uiOrientation;

 if (orientation == ScreenOrientationLockPortrait)
 uiOrientation = CAknAppUi::EAppUiOrientationPortrait;
 else
 uiOrientation = CAknAppUi::EAppUiOrientationLandscape;

 CAknAppUi '''ui = dynamic_cast<CAknAppUi'''>(CEikonEnv::Static()->AppUi());
 TRAPD (error, if (ui) ui->SetOrientationL(uiOrientation); );

 Q_UNUSED(error);
#elif defined(Q_WS_MAEMO_5)
 switch (orientation) {
 case ScreenOrientationLockPortrait:
 setAttribute(Qt::WA_Maemo5PortraitOrientation, true);
 break;
 case ScreenOrientationLockLandscape:
 setAttribute(Qt::WA_Maemo5LandscapeOrientation, true);
 break;
 default:
 setAttribute(Qt::WA_Maemo5AutoOrientation, true);
 break;
 }
#else
 Q_UNUSED(orientation);
#endif
}

Setting Orientation in Qt 4.7.2

Some new attributes were added in Qt 4.7.2 to set screen orientation. They are as follows:

  • Qt::WA_LockPortraitOrientation: //Locks the widget to a portrait orientation, ignoring changes to the display's orientation with respect to the user//
  • Qt::WA_LockLandscapeOrientation: //Locks the widget to a landscape orientation, ignoring changes to the display's orientation with respect to the user//
  • Qt::WA_AutoOrientation: //Causes the widget to change orientation whenever the display changes orientation with respect to the user//

Usage example: In order to lock screen orientation in portrait mode you can do as follows:

QDeclarativeView view;

view.setAttribute(Qt::WA_LockPortraitOrientation);

Important note: Currently these attributes are being used just for Symbian, but probably this will change in future releases to handle Maemo and other platforms as well

Qt Mobility APIs

As said previously, on Introduction, Qt Mobility is a set of Qt APIs that allow developers to write applications that uses some mobile features (e.g. contacts, location and message service) easily and in a cross-platform way, which means that you only need to have Qt and Qt Mobility frameworks installed on your mobile device.

With these APIs the developers can write applications that:

  • Are able to use location services provided by the mobile device, e.g. GPS.
  • Can send SMS to other mobile device
  • Can play musics/videos and manage multimedia content
  • Are able to manage the mobile device contacts

In order to use the Qt Mobility, the developer must use the Qt Mobility namespace on his project.

Ex.:

#include <QMainWindow>
#include <QTextEdit>
#include <QTimer>
#include <qgeopositioninfosource.h>
#include <qgeosatelliteinfosource.h>
#include <qmessagemanager.h>
#include <qmessageservice.h>

QTM_USE_NAMESPACE // Qt Mobility namespace

class MainApp : public QMainWindow
{
 Q_OBJECT

.
.

Here are some Qt Mobility APIs

Service Framework

Implement and find services that are available on the mobile device and perform some operations with them.

Messaging

This API gives the user capabilities to handle SMS, MMS, e-mail and any other kind of "messages" the mobile device can deal with. User can send, search and sort messages, get notified when new messages arrive, compose messages, etc.

Bearer Management

Manages the connectivity state of the mobile device to the network, this includes WLAN and 3G networks

Contacts

Manages all contact information on the mobile device or remotely. With it, user can create, delete and edit contacts

Location

Perform location based operations, like: Retrieve your current GPS location

Multimedia

Manages multimedia content on the mobile device. User can play music and videos, record audio and manage FM radio, for instance.

System Information

Retrieve some system information like Installed Softwares' Version, Features (hardware), Network Status, Display Information, Storage Information, Device Information (battery, profile, SIM card)

Sensors

An API for accessing hardware sensors, like the accelerometer

Camera

Control the camera, capturing images, videos, etc.

When writing an application that uses any Qt Mobility API, the developer must add the respective //*domain*// to MOBILITY variable on the project's .pro file and add the "mobility" option to CONFIG variable.

Below is a table that lists the Qt Mobility domains and associate it with the respective value that must be added to the MOBILITY variable.

Domain Value
Bearer Management bearer
Contacts contacts
Location location
Multimedia multimedia
Messaging messaging
Publish And Subscribe publishsubscribe
Service Framework serviceframework
Sensors sensors
System Information systeminfo
Versit versit
Document Gallery gallery
Organizer organizer
Tactile Feedback feedback


For instance, if you want to write an application that uses Qt Mobility to send messages to some contact, stored on your device, your .pro would look like this:

/* app.pro */
QT ''= core gui network

TARGET = myapp
TEMPLATE = app

SOURCES''= main.cpp

CONFIG ''= mobility # mobility option in CONFIG
MOBILITY''= messaging # domains used on application
 contacts

Note on Symbian Platform

When developing for Symbian with Qt Mobility, you have to add the required capabilities that are required by the specified domains (for further information, take a look here). So, for Symbian the .pro would look like this:

/''' app.pro */
QT''= core gui network

TARGET = myapp
TEMPLATE = app

SOURCES ''= main.cpp

CONFIG''= mobility # mobility option in CONFIG
MOBILITY ''= messaging # domains used on application
 contacts

symbian: {
 TARGET.CAPABILITY = ReadUserData # Symbian capabilities
 WriteUserData  NetworkServices
}


Porting a Desktop Application to Mobile

Let's pick one of the Qt examples to guide our study: the calculator

This example uses QWidgets to build a typical desktop interface. Besides that, the code has the logic strongly coupled to the UI definition, what is not the ideal scenario for porting an application to a different screen size and requires some modifications in the original code.

UI and Reusable Logic

Let's take a look at Calculator, the main class in this example:

class Calculator : public QDialog
{
 Q_OBJECT

public:
 Calculator(QWidget *parent = 0);

private slots:
 void digitClicked();
 void unaryOperatorClicked();
 void additiveOperatorClicked();
 void multiplicativeOperatorClicked();
 void equalClicked();
 void pointClicked();
 void changeSignClicked();
 void backspaceClicked();
 void clear();
 void clearAll();
 void clearMemory();
 void readMemory();
 void setMemory();
 void addToMemory();

private:
 Button *createButton(const QString &text, const char *member);
 void abortOperation();
 bool calculate(double rightOperand, const QString &pendingOperator);

 double sumInMemory;
 double sumSoFar;
 double factorSoFar;
 QString pendingAdditiveOperator;
 QString pendingMultiplicativeOperator;
 bool waitingForOperand;

 QLineEdit *display;

 enum { NumDigitButtons = 10 };
 Button *digitButtons[NumDigitButtons];
};

The Calculator class inherits QDialog, contains the calculator buttons and display. It also contains several slots responsible for processing the inputs and calculating the results. With such scenario, our first task is to separate the UI from the logic.

To make things clearer for the sequence of this tutorial, we simplified a bit the original code of this example. Instead of using the createButton() method, we preferred to make all the creation and connections explicit.

We are going to create a new class for the calculator logic. Let's call it //CalculatorEngine//. This class will receive the slots that were in the Calculator class and will provide a signal emitted everytime a new result is available. This is the interface used by the current QWidget UI and also for the new QML based UI.

class CalculatorEngine : public QObject
{
 Q_OBJECT

 Q_PROPERTY(QString display READ getDisplay NOTIFY displayChanged)

public:
 CalculatorEngine(QObject *parent = 0);

public slots:
 void digitAdded(int digitValue);

 void addition();
 void subtraction();
 void multiplication();
 void division();

 void square();
 void power();
 void reciprocal();

 void equal();
 void point();
 void changeSign();
 void backspace();
 void clear();
 void clearAll();
 void clearMemory();
 void readMemory();
 void setMemory();
 void addToMemory();
 void abortOperation();
 bool calculate(double rightOperand, const QString &pendingOperator);
signals:
 void displayChanged(QString text);

private:
 void additiveOperation(QString selectedOperator);
 void multiplicativeOperation(QString selectedOperator);
 void unaryOperation(QString selectedOperator);

 QString display;
 double sumInMemory;
 double sumSoFar;
 double factorSoFar;
 QString pendingAdditiveOperator;
 QString pendingMultiplicativeOperator;
 bool waitingForOperand;
};

QML code can invoke slots and Q_INVOKABLE methods declared in C+. It is well described in this section of the Qt documentation.

Our new Calculator class without all the logic part looks like this:

class Calculator : public QDialog
{
public:
 Calculator(QWidget *parent = 0);

private:
 QLineEdit *display;

enum { NumDigitButtons = 10 };
 Button *digitButtons[NumDigitButtons];

CalculatorEngine *engine;
};

Now Calculator contains the visual elements: 10 Buttons (which inherits QToolButton) and a QLineEdit. The class implementation now concentrates in the creation and layout of the UI. All the logic was moved to

CalculatorEngine

, which inherits QObject and has the slots used by Calculator to connect its signals.

With the original code refactored, we have two clearly separated parts of the application: the current desktop UI and the internal logic. The logic part will be reused in the mobile version of the application.

Reusing Existing Models from standard Qt in QML

If your application has a proper separation between the UI and it's logic the transition should not be hard and the UI will be the unique code rewritten. To port your app to QML there are two basic steps to do.

The first one is to create a QDeclarativeView and make it load the root QML element of your application. The second is to export your current application logic to let QML use it.

The following snippet loads the Calculator.qml element and makes our

CalculatorEngine

visible to QML through the id

calcEngine

.

int main(int argc, char *argv[])
{
 QApplication app(argc, argv);

CalculatorEngine calcEngine;
 QDeclarativeView view;
 QDeclarativeContext '''context = view.rootContext();
 context->setContextProperty("calcEngine", &calcEngine); // Exporting logic part of our application to QML
 view.setSource(QUrl(":Calculator.qml")); // Set the QML file that is inside a Qt Resource file
 view.show();

 return app.exec();
}

Now that we have a CalculatorEngine object visible in QML context we must use its properties, slots and signals to link with our UI. In our example, the QML code binds the display property from the model into a text element that we call Display. Javascript is also used to map the buttons in the layout with the actions that must be performed, calling the methods which the CalculatorEngine exports to QML.

// delegates of each model's item will be created
// to fill the grid view
delegate: Component {
 Item {
 width: grid.cellWidth
 height: grid.cellHeight
 Button {
 anchors.fill: parent
 anchors.margins: 3
 icon: modelData.source
 onClicked: {
 // we are using javascript to map the buttons
 // with the actions it must perform
 var currentItem = Calc.landscapeButtonsModel[index];
 var action = currentItem['action'];
 action();
 }
 }
 }
}

Using the Qt Resource System

Qt provides a platform-independent resource system, useful to store important files needed by the application. It is a good option to guarantee the deployment and to avoid problems due to the corruption of one of these files. The Qt Resource System can generate a binary containing the files, then the application loads the content from this binary. Another possibility is to embed this generated resource file into the application binary. In this example, we use the second option to embed the images and QML files - check calculator.qrc. For more details regarding can be found in the Qt Resource System documentation


Deploying to Mobile using Nokia Qt SDK

Creating a new Mobile Application

While creating a new application you will be prompted to choose what kind of application you are about to create. Choose Qt Quick application:

B1.png

Continue with the Wizard and choose what targets your application will have. If you want to deploy for Symbian and Maemo click in the respective checkbox.

B3.png

In application options you can change some target variables. You can set at this time the icon your application will use in Symbian and Maemo platforms and also change the UID3 for Symbian.

The UID3 is an unique value that identifies your application. By default QtCreator will set a temporary UID which is just useful for development purpose. However, in order to submit your application to OVI store, you will need to get a [UID].

B5.png

Testing and Deploying

After creating the project you can select the desired target in the left panel. In the case below, a Symbian Device is selected. Note that in the device picture, there is a green circle indicating the device has been detected. QtCreator automatically detects new devices connected in the USB ports.

  • Note: In order for the device to be detected, it must have 'Nokia Ovi Suite' option active in the USB settings and it must have also TRK installed (Symbian^3 TRK sis file can be found at <PATH_TO_SDK>^3\TRK).

B13.png

If the device has been detected, you can test your application at any time in the target device, clicking in the green arrow in the left panel. This will build (if necessary) your application, send it to device and launch it.

  • Note: You can also debug your application in the device clicking in the green arrow below the first one (the arrow with a bug).

The output is like the following:

B16.png

When you are ready to deploy your application you can click in 'Deploy All'. This will generate installation files for your target device. Generally the generated files are placed in the build directory. For Symbian, for example, you can send the generated .sis files to your device, so you can install and run it at any time.

B14.png