SailfishOS Development: The Basics

  • Tutorial
Hello! Last week I wrote about how to start developing for the Sailfish OS mobile platform . Today I would like to talk about the life cycle of Sailfish applications, about creating application pages and managing them, as well as about some specific features of mobile applications that should be considered when developing for Sailfish OS, in particular, device orientation control.

The SailfishOS SDK (which was described in a previous article) includes Sailfish Silica, a QML module used to create Sailfish applications. This module contains QML components that look and are managed in accordance with the application standards for Sailfish. Among other things, Sailfish Silica also contains tools for creating specific elements of Sailfish applications, such as, for example, Cover Page , which were slightly touched on in a previous article . In order to use the Sailfish Silica module and its tools and components, you just need to import this module into QML application code files. This will be shown in the example a bit later.

ApplicationWindow


The main QML of any Sailfish application is the ApplicationWindow component , which describes the application window and contains the application user interface and is generally the main and required entry point for loading the Sailfish application. Application screens are implemented using the Page component . At the same time, ApplicationWindow contains the initialPage property , which allows you to set the initial screen of the application. Thus, the minimum application for the Sailfish platform will look like this:
import QtQuick 2.2import Sailfish.Silica 1.0
ApplicationWindow {
    initialPage: Component {
        Page {
            Label {
                text: "Привет, Хабр!"
                anchors.centerIn: parent
            }
        }
    }
}

This application will display one simple page with the words Hello, Habr! in the middle. It is not necessary to describe the page itself directly in the description of the property; you can simply pass there the id of the page or URL of the file where the page is described.

Page stack


In addition to the start page, ApplicationWindow also contains the pageStack property - containing the Page Stack component that allows you to control the stack of screens (or pages) of the application. In the example above, the Page Stack consists of only one page, which was put on this stack using the initialPage property . You can add a page to the top of the page stack (and, accordingly, display it on the screen) using the push () methodpassing it as an argument the path to the QML file with the page or id of this page. We will expand our example by adding a button under the inscription, when clicked, it will go to the next page (we will assume that the code for this page is contained in the SecondPage.qml file ):
ApplicationWindow {
    initialPage: initialPage
    Page {
        id: initialPage
        Label {
            id: helloLabel
            text: "Привет, Хабр!"
            anchors.centerIn: parent
        }
        Button {
            text: "Следующий"
            anchors.top: helloLabel.bottom
            anchors.horizontalCenter: parent.horizontalCenter
            onClicked: pageStack.push(Qt.resolvedUrl("SecondPage.qml"))
        }
    }
}

Instead of a single page, you can also pass an array containing several pages to the push () method . In this case, all these pages will be added to the stack. This method has two more optional arguments. The first is the page parameters, and the second is the type of operation that determines whether to use animation when moving to a given page. It can be one of two values: PageStackAction.Animated or PageStackAction.Immediate . This argument is also present in all other Page Stack methods that are responsible for changing the current page (for example, the pop method, which will be discussed later). By default, all transitions are carried out with animation, which is convenient. If for some reason the animation is not needed during the transition, you can call the method as follows:
pageStack.push(Qt.resolvedUrl("SecondPage.qml"), { }, PageStackAction.Immediate)

In order to return to the previous page, you need to call the pop () method on the Page Stack component . This method will remove the top page from the stack and, accordingly, go back to the page. Optionally, the method can also specify some page that is already on the stack. In this case, the method will remove from the stack all the pages located on the stack above the specified one. It is also worth noting here that the back transition in the Sailfish OS platform is implemented using a horizontal swipe from the left edge of the screen, however, in Sailfish Silica this functionality is already implemented and the pop () method is automatically called with this gesture , which is convenient since the developer does not need to attach efforts to implement standard functionality.

In addition to the methods described above, Page Stack also provides properties such as depth (the number of pages on the stack) and currentPage (the current page), as well as methods such as:
  • replace () - replaces the current top page on the stack,
  • pushAttached () - adds the specified page to the top of the stack, but does not go to it (the user can go to this page using a horizontal swipe from the right edge of the screen),
  • navigateForward () and navigateBack () - go to the next or previous page in the stack relative to the current one, without changing the stack itself.

Of course, not all methods and properties of the Page Stack component are described above . However, the main ones that may come in handy in the first place, I tried to describe. You can read more about the component in the official documentation .

Dialog


The dialogs in Sailfish OS are the same pages. However, they are intended to display to the user some data with which he may agree or disagree. And he can do this either by pressing the buttons, or by swipe left (agree) or right (refuse). Since the dialog is a special page, in Sailfish Silica the Dialog component is “inherited” from the Page component . In the previous example, we replace the transition to the next page to show the minimum dialogue. To do this, we describe the dialog in our ApplicationWindow :
ApplicationWindow {
    initialPage: initialPage
    Page {
        id: initialPage
        Label {
            id: helloLabel
            text: "Привет, Хабр!"
            anchors.centerIn: parent
        }
        Button {
            text: "Следующий"
            anchors.top: helloLabel.bottom
            anchors.horizontalCenter: parent.horizontalCenter
            onClicked: pageStack.push(dialog)
        }
    }
    Dialog {
        id: dialog
        Label {
            text: "Я - диалог"
            anchors.centerIn: parent
        }
    }
}

Even a dialog is shown by adding it to the page stack. If you start the application and click on the button, then we will see the following:

As you can see, outwardly this minimal dialogue is no different from a regular page. However, its behavior is different: if you swipe left or right (or click on the white areas in the upper left or lower corner), the dialog will close and the application’s start page will be displayed. In this case, depending on the direction of the swipe, the corresponding dialog signal ( onRejected or onAccepted ) is called. This can be checked by adding signal handlers to the dialog that will change the text on the main page:
onAccepted: helloLabel.text = "Согласился"
onRejected: helloLabel.text = "Отказался"

Also on the dialog, using the DialogHeader component, you can add the standard “Cancel” and “Accept” buttons at the top of the dialog. At the same time, to display these buttons, simply add an empty component. Optionally, you can also specify the title property , which determines the text that will be located under the buttons. This text is usually used to display a question to the user. Add DialogHeader to the dialog from the example above:
Dialog {
    id: dialog
    DialogHeader {
        title: "Простой диалог"
    }
    Label {
        text: "Я - диалог"
        anchors.centerIn: parent
    }
    onAccepted: helloLabel.text = "Согласился"
    onRejected: helloLabel.text = "Отказался"
}

Now it looks like this:


It should be noted that when you run the example above, you can see the following warnings:
[W] unknown:189 - file:///usr/lib/qt5/qml/Sailfish/Silica/DialogHeader.qml:189: TypeError: Cannot read property 'backIndicatorDown'ofnull
[W] unknown:194 - file:///usr/lib/qt5/qml/Sailfish/Silica/DialogHeader.qml:194: TypeError: Cannot read property 'backIndicatorDown'ofnull
[W] unknown:247 - file:///usr/lib/qt5/qml/Sailfish/Silica/DialogHeader.qml:247: TypeError: Cannot read property 'forwardIndicatorDown'ofnull
[W] unknown:242 - file:///usr/lib/qt5/qml/Sailfish/Silica/DialogHeader.qml:242: TypeError: Cannot read property 'forwardIndicatorDown'ofnull

They have no effect on functionality, but I could not find the reason for these warnings on the Internet. This seems like a minor flaw as Sailfish Silica is still under development. Let's hope that in the future these shortcomings will be fixed.

You can read more about Dialog in the official documentation .

Application Lifecycle and Cover


The application life cycle for Sailfish OS is quite simple. Since the platform implements full multitasking, an application can be in one of three states: either it is not running at all, or it works in the background (background), or works in the active mode (foreground). Moreover, in active mode, the application is deployed in full screen, while in the background the application is represented by its miniature (called cover) on the main screen of the system (this was written a little in the previous article ). You can determine which state the application is in using the Qt.application.state property . If the application is in the background, this property takes the value Qt.ApplicationInactive . Otherwise, Qt.ApplicationАctive. The state of the application must be known and used, for example, to stop heavy computing tasks or animations when the application is in the background so as not to waste system resources.

When the application is in the background, its thumbnail is displayed on the main screen - cover. You can describe this cover in the application code using the Cover component . By default, the application already has a cover installed, which looks like this:

You can set your cover using the cover property of the ApplicationWindow component . We rewrite the example above so that when you click on the buttons in the dialog, the text does not change on the main screen of the application, but on its cover:
ApplicationWindow {
    initialPage: initialPage
    cover: cover
    Page {
        id: initialPage
        // Описание главной страницы приложения...
    }
    Cover {
        id: cover
        transparent: true
        Label {
            id: coverLabel
            text: "Привет, Хабр!"
            anchors.centerIn: parent
        }
    }
    Dialog {
        id: dialog
        DialogHeader {
            title: "Простой диалог"
        }
        Label {
            text: "Я - диалог"
            anchors.centerIn: parent
        }
        onAccepted: coverLabel.text = "Согласился"
        onRejected: coverLabel.text = "Отказался"
    }
}

Of course, this example is intended only to familiarize readers with the work with the Cover component . In a real application, cover represents the application itself on the main screen of the system. Therefore, he must, firstly, present the application so that the user at a glance can find out that this is a thumbnail of this particular application. Secondly, the cover should contain a minimum amount of the most important information, since the user can see all the details by opening the application itself. And finally, thirdly, as shown in the example above, the application cover should change, following the state of the application itself and its data.

Sailfish OS also allows cover to perform demanding tasks: animations, calculations, etc. However, it is worth noting that since the application can be in the background along with other applications and its thumbnail is displayed along with the others, these tasks should not constantly load the system and should be performed periodically. For example, a weather application should update its cover only when new weather data arrives from the server. In addition, you should not perform such tasks when the thumbnail is not visible to the user (for example, when the main screen is closed). To do this, you can use the status property , which takes one of the following values:
  • Cover.Inactive - the thumbnail is not visible and the user cannot interact with it,
  • Cover.Active - the thumbnail is visible and the user can interact with it,
  • Cover.Activating - the thumbnail goes into the status of Cover.Active ,
  • Cover.Deactivating - the thumbnail goes into the status of Cover.Inactive .


In addition, cover can also provide the user with the ability to control the application directly from the thumbnail itself. To do this, using the CoverActionList component , inside which the CoverAction components are defined , you can add buttons to the thumbnail. For example, for a music player, these can be the stop and play buttons for a song, as well as the buttons for moving to the next or previous track. Add a control button to the thumbnail from our example. This button will change the caption on our thumbnail:
Cover {
    id: cover
    transparent: true
    Label {
        id: coverLabel
        text: "Привет, Хабр!"
        anchors.centerIn: parent
    }
    CoverActionList {
        CoverAction {
            iconSource: "image://theme/icon-cover-next"
            onTriggered: coverLabel.text = "Следующий!"
        }
    }
}

You can read more about Cover in the official documentation .

Device orientation


Like other mobile devices, devices based on the Sailfish OS platform support two possible screen orientations: portrait and landscape. In order to find out the current orientation of the device, you can use the isPortrait and isLandscape properties of the Page component , or the orientation property , which takes one of the following values: Orientation.Portrait , Orientation.Landscape , Orientation.PortraitInverted or Orientation.LandscapeInverted . Also, if it is important to check, for example, that the device is in portrait orientation, and whether it is inverted or not, it does not matter, then you can compare the value of the propertyorientation with mask Orientation.PortraitMask . For landscape mode, there is a similar mask Orientation.LandscapeMask .

If you want the application to run only in certain orientations, it is possible to use the property allowedOrientations component ApplicationWindow , which you can specify in which orientation should run the application. You can specify the same values ​​as the values ​​that the orientation property returns , as well as the Orientation.LandscapeMask , Orientation.PortraitMask or Orientation.All masks . The default value of the allowedOrientations propertydepends on the specific device, so if it is important for the application that it should work in certain orientations (or in any), then it is better to indicate this explicitly. In addition, this property can also be specified for the Page component , then the rule of permitted orientations will be applied only to a specific page.

That's all. However, I would like to note that despite the fact that in the examples of this article all the code was described in one file, when writing real applications it is best to describe each page and cover in a separate QML file. This will speed up the launch of the application, since it will prevent compilation of all QML components at application startup.

In the next article, I will talk about the other components that make up Sailfish Silica.

Article author: Denis Laure

Also popular now: