The Ubuntu SDK contains a full development environment, complete with IDE and support for running apps on your desktop as well as on Ubuntu phone and tablet devices.
With the SDK, you can write apps using a number of different frameworks:
In this article, I will write an application using QML, which the majority of new Ubuntu app developers are using to write their apps.
I'll begin by looking at what QML is, how it works, and then I will show you how to write an app that I wrote and uploaded to the Ubuntu Software Center called Sleepy Time, that plays soothing sounds for helping babies, children, and adults to sleep.
To get started, load the Ubuntu SDK and create a new QML project. To do this click File | New File or Project… . Next, ensure that Ubuntu is selected in the left pane and click Simple Touch UI , then Create .
In the next dialog, name the project sleepy and select a place where the project will be created. From the Add to the version control combo box, select Bazaar so you can automatically version your project and then click Finish . (See the box "Installing the Ubuntu SDK" for more information.)
Installing the Ubuntu SDK
Installing the Ubuntu SDK is simple; the full SDK is available in the Ubuntu Software Center. Simply search for Ubuntu SDK and install it on your computer. You can then start the SDK by searching for 'SDK' in the Ubuntu dash.
A series of files has now been created, and these files are shown in the left side panel. Double-clicking on a file loads the file into the editor. Double-click on sleepy.qml if it is not already loaded, and you will see code shown in Listing 1 in the editor (I have removed comments shown with /* */ and // to be concise).
Listing 1
sleepy.qml
01 import QtQuick 2.0 02 import Ubuntu.Components 0.1 03 import "components" 04 05 MainView { 06 objectName: "mainView" 07 08 applicationName: "uutest" 09 10 width: units.gu(100) 11 height: units.gu(75) 12 13 Page { 14 title: i18n.tr("Simple") 15 16 Column { 17 spacing: units.gu(1) 18 anchors { 19 margins: units.gu(2) 20 fill: parent 21 } 22 23 HelloComponent { 24 id: label 25 objectName: "label" 26 27 text: i18n.tr("Hello..") 28 } 29 30 Button { 31 objectName: "button" 32 width: parent.width 33 34 text: i18n.tr("Tap me!") 35 36 onClicked: { 37 label.text = i18n.tr("..world!") 38 } 39 } 40 } 41 } 42 }
You can now run the project by clicking the green arrow in the left side panel near the bottom of the window. Running the app displays a Tap Me button that, when tapped, changes the text in the box above.
At the top of this code snippet, I import some libraries of functionality, much like other programming languages.
One key library here is Ubuntu.Components , which is a collection of user interface controls used to build apps. To see the range of components available, click Tools | Ubuntu Touch | Ubuntu Touch Showcase Gallery .
If you do this, a new project is loaded and you can press the green arrow to display the components and also browse the code. When you are finished poking around with the showcase gallery, right-click the top sleepy entry in the sidebar (with the blue folder and arrow to the left) and select Set "sleepy" as Active Project , so you are working on that project (this ensures that, when you run the app, it will run sleepy and not the components gallery).
In QML, you see a series of nested user interface components, each nested within curly braces ({} ). The MainView project is the root container used in all apps, and nested inside is a Page , which is an area that you can fill with the content of your app.
Within the Page is a Column element, which simply organizes its children vertically. And, within Column , you have something called HelloComponent .
This component is defined elsewhere (in the components sub-directory). You will notice that I use the same file name as the component name (HelloComponent is components/hellocomponent.qml ). This method of embedded components provides a handy way to share code across your app and re-use it. I will discuss this component more a little later.
Beneath the HelloComponent is a Button , which is another Ubuntu component that simply displays the button you click to change the text of the HelloComponent .
Within each component are settings that you can configure. As an example, in Button , you set the name for the object that you can reference (objectName ) and set the text displayed on the button (text: ).
Note that, when setting the text, you wrap it in i18n.tr() , which will mark the string as a translatable string that you can translate with gettext .
A key feature of the Ubuntu user interface toolkit is the ability to scale to all form factors in a world defined by users with multiple devices.
The approach taken has been to define a new unit type, the grid unit (gu for short). Grid units translate to a pixel value depending on the type of screen and device that the application is running on. Check out Table 1 for some examples.
Table 1
Grid Units for Several Devices
Most laptops | 1 gu = 8 px |
Retina laptops | 1 gu = 16 px |
Smartphones | 1 gu = 18 px |
In the previous code, you can see that I set the spacing in between children in the Column to 1gu and set the margins as part of the anchors (the margins are anchored to the parent component, the Page ). I also set the size of the app itself with the "width" and "height" lines in the MainView.
Before I move on, I'll quickly cover the HelloComponent . In components/hellocomponent.qml , the code looks like what is shown in Listing 2.
Listing 2
hellocomponent.qml
01 import QtQuick 2.0 02 import Ubuntu.Components 0.1 03 04 UbuntuShape { 05 width: 200 06 height: width 07 08 property alias text : myText.text 09 10 Label { 11 id: myText 12 anchors.centerIn: parent 13 } 14 }
Here, I use an UbuntuShape component (a nice rounded square) and then embed a Label component, which has an id of 'myText' , and I center the text. You may also notice that I don't actually set what the text is. This is where properties come in.
The Label component has a 'text' property that can be used to set the text of the label, but I really want to set this value outside of the HelloComponent and in my other source file.
That's why I have the following line:
property alias text : myText.text
It creates an alias for the myText.text property that is available outside of the component with the alias text .
To see this in action, go back to sleepy.qml , where you can see:
onClicked: { label.text = i18n.tr("..world!") }
This block appears within the Button component and is how you deal with interactions in QML.
Essentially, every component has a range of different signals – that is, types of interaction with that component that when triggered can run some code.
In this case, you see a clicked signal in Button , and when I use the onClicked block (adding on before the signal creates a block to respond to a signal), the code inside it is executed when the button is clicked. It just so happens that this code references the label object (HelloComponent ), and you can set the label.text property – the alias to the text that you want to appear when the button is clicked.
Now that you have a firm understanding of this generated program and the core fundamentals of QML under your belt, I'll show how to make this app into something useful. In this case, I will create a simple sound board that can play a range of different atmospheric sounds to help babies, children, and adults go to sleep.
To begin, in sleepy.qml , remove the entire Column block and set the title of the Page component to Sleepy . When you run the app now, you should see an empty window other than the Sleepy header. This has been removed because I'm going to use a grid to show the sounds instead.
The sounds will be displayed as a bunch of Ubuntu-themed squares, each of which has an icon and some text and, when you click on each rectangle, the sound will play and the square will change color to indicate the sound is playing. With this app, you will be able to mix sounds together if multiple buttons are pressed.
To achieve this outcome, you need to roll your own sound button component in much the same way you did with HelloComponent . To do this, click File | New File or Project… and, in the left panel, click Qt and then QML File (Qt Quick 2) . Call the file SoundButton.qml (case-sensitive) and add it to the project.
Now, you can add the code in Listing 3 to the source file. Although this may look like a lot of code, much of it should look familiar based on what you've learned so far. I'll go through it step by step.
Listing 3
sleepy.qml – Part 1
01 import QtQuick 2.0 02 import QtMultimedia 5.0 03 import Ubuntu.Components 0.1 04 05 UbuntuShape { 06 id: box; 07 antialiasing: true; 08 radius: "medium" 09 10 property alias color: box.color; 11 property alias description: label.text; 12 property alias imageSource: image.source; 13 property alias soundSource: sound.source; 14 property var state: false; 15 16 Image { 17 id: image; 18 anchors.horizontalCenter: parent.horizontalCenter; 19 anchors.verticalCenter: parent.verticalCenter; 20 width: parent.width * 0.8; 21 height: parent.height * 0.8; 22 fillMode: Image.PreserveAspectFit 23 } 24 25 Label { 26 id: label; 27 anchors.horizontalCenter: image.horizontalCenter 28 anchors.top: image.bottom 29 text: "Hello, world!" 30 fontSize: "medium" 31 } 32 33 Audio { 34 id: sound; 35 36 onStopped: { 37 box.color = "#32222C" 38 if (box.state == true) { 39 box.color = UbuntuColors.warmGrey 40 sound.play() 41 } 42 } 43 } 44 MouseArea { 45 anchors.fill: parent; 46 onPressed: { 47 if (box.state == false) { 48 box.state = true; 49 sound.play(); 50 box.color = UbuntuColors.warmGrey 51 52 } 53 else if (box.state == true) { 54 box.state = false; 55 sound.stop() 56 box.color = "#32222C" 57 } 58 59 } 60 61 } 62 }
First, you add the imports, but there is one new import here: Qt.Multimedia , which provides a range of audio and video features. This is included to be able to play audio files when the user clicks on the sound buttons.
Next, you create the SoundButton , which comprises an UbuntuShape and within it an Image and a Label underneath it. Inside the Image component are some properties that anchor the image to the center of the UbuntuShape .
Next, the width and height settings add a small border around the edge of the image. In the Label , you set the text to some text that will be replaced later, then set the font size and the alignment.
Now, I'll show how the audio in this component works. When the user clicks the button, you want not only to play the sound but also to stop playing the sound when the user clicks it again. As such, you need to track the state of whether the sound is currently playing or not. A common use case for this app is playing a sound all night while sleeping; to do that, you'll want to loop each sound so it doesn't stop when the sound has finished playing.
Start by adding the Audio component as part of the UbuntuShape , which doesn't do much other than identify that audio can work as part of this component. You may have noticed this line in the top-level UbuntuShape properties:
property var state: false;
This is the variable you will use to track whether the audio of each instance of the sound button is playing. I set this to false by default as the sound is not playing when the component is created.
Now I'll look at how playback works. To begin, create an Audio component that doesn't include much inside it. The presence of the component in UbuntuShape means that this component has the ability to play music.
Inside Audio is an onStopped signal handler for the Audio stopped signal that is fired when either an audio file is stopped with the stop() function or reaches the end of the file and thus stops playback.
In onStopped , check whether the playing state is set to true and, if it is, change the color of the box to UbuntuColors.warmGrey (part of the official Ubuntu color palette), which indicates the sound is playing.
Then, run the play() function to play it. This will handle the case of looping the audio (if the state was set to true , then you know the audio was already playing and as such needs to loop).
Next, I'll take a look at the last component, the MouseArea . This component provides a clickable area throughout the entire UbuntuShape , and each of its children.
Here, you respond to the pressed signal in onPressed and check whether the state of playback is false . If it is (no sound is playing), run the play() function, set the color of the UbuntuShape to warm grey, and set the state to true . Conversely, if the state is true , run stop() to stop playback, set the color to the non-playback color, and set the state to false .
Before adding SoundButtons to the main UI, first create a media/ folder in the sleepy project directory and add some icons and sound files.
I have added the following, for example:
ls media/ bigwaves.png bigwaves.mp3 calmwaves.mp3 calmwaves.png \ chimes.png chimes.mp3 city.png city.mp3 countryside.png \ countryside.mp3 whitenoise.png whitenoise.mp3
Here, you can see that I have the same file name for each .png and .mp3 combination. You can assemble these in your project directory with your normal file manager.
Now, you can update sleepy.qml with the SoundButtons . Add the content of Listing 4 inside your Page component. Here, you create an Item component with a Grid inside it. First, in the Item , do a little math to calculate the size of the buttons based upon the number of columns (3) and rows (2) you want. Inside the Grid , you then create each of the SoundButtons , which reference the QML from earlier.
Listing 4
sleep.qml – Part 2
01 Item { 02 anchors.fill: parent 03 04 id: wrapper 05 property int n_columns: height > width ? 2 : 3; 06 property int n_rows: height > width ? 3 : 2; 07 property int button_size: Math.min (width / n_columns, 08 height / n_rows) * 0.9; 09 property int button_radius: 10; 10 property int button_xspacing: (width - button_size * 11 n_columns) / (n_columns + 1); 12 property int button_yspacing: (height - button_size * 13 n_rows) / (n_rows + 1); 14 15 Grid { 16 x: wrapper.button_xspacing; 17 y: wrapper.button_yspacing; 18 columns: wrapper.n_columns; 19 rows: wrapper.n_rows; 20 21 columnSpacing: wrapper.button_xspacing; 22 rowSpacing: wrapper.button_yspacing; 23 24 SoundButton { 25 width: wrapper.button_size; 26 height: wrapper.button_size; 27 radius: wrapper.button_radius; 28 color: "#32222C" 29 description: "White Noise" 30 imageSource: "media/whitenoise.png"; 31 soundSource: "media/whitenoise.mp3"; 32 } 33 SoundButton { 34 width: wrapper.button_size; 35 height: wrapper.button_size; 36 radius: wrapper.button_radius; 37 color: "#32222C" 38 description: "Wind Chimes" 39 imageSource: "media/chimes.png"; 40 soundSource: "media/chimes.mp3"; 41 } 42 SoundButton { 43 width: wrapper.button_size; 44 height: wrapper.button_size; 45 radius: wrapper.button_radius; 46 color: "#32222C" 47 description: "Big Waves" 48 imageSource: "media/bigwaves.png"; 49 soundSource: "media/bigwaves.mp3"; 50 } 51 SoundButton { 52 width: wrapper.button_size; 53 height: wrapper.button_size; 54 radius: wrapper.button_radius; 55 color: "#32222C" 56 description: "Calm Waves" 57 imageSource: "media/calmwaves.png"; 58 soundSource: "media/calmwaves.mp3"; 59 } 60 SoundButton { 61 width: wrapper.button_size; 62 height: wrapper.button_size; 63 radius: wrapper.button_radius; 64 color: "#32222C" 65 description: "City Ambiance" 66 imageSource: "media/city.png"; 67 soundSource: "media/city.mp3"; 68 } 69 SoundButton { 70 width: wrapper.button_size; 71 height: wrapper.button_size; 72 radius: wrapper.button_radius; 73 color: "#32222C" 74 description: "Country Ambiance" 75 imageSource: "media/countryside.png"; 76 soundSource: "media/countryside.mp3"; 77 } 78 } 79 }
Inside each SoundButton , you pass a description that sets the text of the Label in the SoundButton . You also pass an imageSource and soundSource that map to the aliases in the SoundButton component, which in turn set Image.source and Audio.source .
Now when you run the app, you should see the buttons, be able to start and stop sounds, and have everything work as expected.
Before you finish, you might want to add one final flourish: a nice gradient behind the MainView. To do this, you can add the following lines after height: units.gu(75) in MainView :
backgroundColor: "#741266" footerColor: "#bd0776"
You will now see a nice smooth gradient behind your app.
I have really only just scratched the surface of the wealth of functionality available in the Ubuntu SDK and the comprehensive toolkit that is available as part of the Ubuntu Components and QML (see the "Publishing Your App" box for more information).
Publishing Your App
When you have written your first app and want to share it with the world, getting it in the hands of Ubuntu users is simple. Ubuntu recently opened up the Beta of the new Ubuntu Touch app publishing process.
Publishing an app is as simple as generating a package (which is just a few clicks in the Ubuntu SDK) and then uploading it to the Ubuntu Developer Portal [1], where you can add information about the app, screenshots, an icon, and more.
You can read full details of how to get started on the publishing section of the portal [2].
Fortunately, a lot of content is available online, and I recommend that you head over to the Ubuntu Developer Portal [1], where you can find API documentation, cookbooks, tutorials, and more.
You might also consider joining the community of Ubuntu app developers (see the "Join the Community" box for details). Have fun writing some awesome apps! l
Join the Community
A large and actively growing community of Ubuntu App Developers is forming. Be sure to get started on your journey [1] but also join the Google+ Community [3] and the Facebook community [4].
Infos