(FreeCourseWeb - Com) Python GUI Develop Android Applications Using Python, QT and PyQt5

Download as pdf or txt
Download as pdf or txt
You are on page 1of 49
At a glance
Powered by AI
The key takeaways are about developing Android apps using Python, Qt and PyQt5.

The main parts of the book are an introduction, basics of PyQt and Qt, QML syntax, deploying apps to Android and a conclusion.

The author has experience with C, Assembler, Clipper, PowerBuilder, Java, C#, Objective-C, C++ and now mainly uses Python.

Table of Contents

1. Table of Contents
2. Preface
1. Copyright
2. Motivation
3. Who This Book Is For
4. Who This Book Is Not For
5. How This Book Is Organized
6. Conventions Used In This Book
7. Using Code Examples
8. How To Contact Me
9. Acknowledgements
3. Part I - Prolog
1. Installation
2. Development Environment Setup
1. Python
2. Coderunner
3. QML
4. VSCode Settings
3. First App
4. Summary
4. Part II - Basics
1. Many Different Approaches
2. Hello World (QtWidgets)
3. Hello World (QtQuick)
4. Combining QWidget and QML
5. Summary
5. Part III - QML
1. QML Syntax
1. Id
2. Child Objects
3. Comments
4. Import Statements
2. Properties
3. Property Binding
4. Signals
1. Receiving signals and signal handlers
2. Property change signal handlers
3. Connections
4. Attached signal handlers
5. Signals for a custom QML type
5. Functions
6. Interaction between QML and Python
7. Screen size
8. Positioning Items
9. Controls
10. Summary
6. Part IV - Deploment
1. Deploying a dynamic app to Android
2. Install pyqtdeploy 2.4
3. Install Java JDK 8
4. Install Android SDK
5. Install Android NDK
6. Install Qt
7. Download Source packages
8. Create the build script
9. Create the build.py script
10. Create the resource file
11. Create a project file
12. Create sysroot.json
13. Build the APK
14. Install APK to device
15. Summary
7. Afterwords
8. About The Author
Preface
Copyright
Develop Android Applications using Python, Qt and PyQt5
by Olaf Art Ananda
(C) Copyright 2020 Olaf Art Ananda. All rights reserved.

Motivation
After coding for over 30 years now, I came from C, Assembler,
Clipper, Powerbuilder, Java, C#, Objetive-C, C++ to Python. And
guess what...
I love Python
It would be easy for me to develop native apps using Java, C++ or
Objective-C and I am also able to learn Kotlin, Dart or Swift, but things
are much easier when you just use Python. I have done a Django
tutorial which is a web framework and the ease they have build a
model class to be stored in an SQL server has stunned me. I just had
to write a model class with a few line of code, then I had to run a
scaffolding job which creates corresponding tables on the SQL server
and after that I could run the Python interpreter, import my model
class, write some properties and save the instance. No SQL coding at
all. I have been hooked :-) Then I learned about generators,
comprehension and meta programming and Python got me.

Btw...to be able to write this book I wrote an application called


EbookCreator. This app is also using PyQt5, QtWigets and I am using
QML to serialize book projects, it is open source and you may have a
look at the source code for inspiration. You are able to find the
sources on github.

Yesterday I have seen a video with "Uncle" Bob Martin about


programming in the future. He says that every 5 years the count of
software developers on this planet double. That means that there are
at least 50% of all developers that are inexperienced. He also said
that we as a mature developer are responsible when for example a
self driving car is killing other people because of software errors. That
heard today I started to teach other developers and I am starting with
this book.

Who This Book Is For


If you are able to write basic Python code and you are interested in
developing apps with user interfaces for Android in Python this book is
right for you. This book does not assume that you are familiar with Qt.
If you want, try out all of the examples in this book it would be a great
benefit if you are also working on a Linux machine like me, but MacOS
and Windows should also be ok if your are able to search for help in
the internet for OS specific differences.

Who This Book Is Not For


If you are new to Python and programming you should consider
reading beginner books for Python and watch tutorials on Youtube
before continuing to read this book. I am not going into Python specific
details in this book.

How This Book Is Organized


Here are the main topics in each part of the book. First in Part I you
are going to install all necessary software to be able to build
applications with PyQt5 and Python. In Part II you will learn about two
different approaches to build apps. In Part III you will learn to code
apps using QML and Python.
In Part IV you will learn to to create an APK which you can deploy to
an Android device.

Conventions Used In This Book


The following typographical conventions are used in this book.

Italic

Used for filenames and paths

Constant width

Used for code samples

Also I am using the Pascal convention to put brackets so that the code
is more readable. This is a habit from my C/C++ and C# programming
experience.

1 Item
2 {
3 SubItem
4 {
5

6 }
7 }

Using Code Examples


All code examples are available on github .

How To Contact Me
If you have any questions or comments don't hesitate to send me an
email with your questions or comments to the following address:
[email protected]

Acknowledgements
First of all I am very thankful to my body which points me to the right
ways in life. I know this sounds a bit weird but after learning to be a
mechanic many years ago my back hurts after I have worked way too
hard and after a half year being ill I decided to study to build machines
and then I have learned to do programming just to solve a
mathematical problem. Since that time I decided to be a software
developer. Then 5 years ago my body told me to stop working for
profit after I had my second burnout. Now I have got much time to
develop open source software, try out new stuff and write books like
this.

I am very thankful to Guido van Rossum for inventing Python in 1991.

I am also very thankful to all Pythonistas out there who are creating
tutorials and videos on YouTube where one is able to learn Python for
free.
Part I - Prolog
Installation
Here we are going to install all necessary software to be able to write
code and test it at least on the desktop. To be able to also deploy the
app to other devices we will install additional software in the
deployment chapter later in this book.

I assume that you already have installed Python3 and Pip. If not you
should find all necessary information on the Python website. We need
version 3.7.
I also assume that you able to install software using pip.

First we will install PyQt5 which comes together with Qt5 to be able to
code applications for the desktop.

1 user@machine:/path$ pip3 install PyQt5


2 user@machine:/path$ pip3 install PyQtWebEngine

Then we will install Visual Studio Code which is free and open source
and has many good extensions to develop Python code. You can
download VSCode here. I assume that you are able to install VSCode
on your own. Otherwise you will find installation instructions on this
website. You can also use apt if you are on a Linux system. Use
google search if there are some prerequisites missing.

1 user@machine:/path$sudo add-apt-repository "deb [arch=amd64]


https://packages.microsoft.com/repos/vscode stable main"
2 user@machine:/path$ sudo apt update
3 user@machine:/path$ sudo apt install code

In the next chapter we will setup the environment to be able to write


our first app.
Development Environment Setup
After installing VSCode you should install some nifty extensions like
these.

Python

Python is a syntax hiliter, debugger and linter. Sometimes it also


shows you the correct intellisense.

Coderunner

With coderunner you are able to start your app with just on click.

You just have to click the


"Play" button.

QML

QML is useful to syntax hilite QML code.

VSCode Settings

You can open the settings file using this menu.


And then click "{}" on upper right site of the screen to open the json
file.

Here are some useful setting which I have entered into the
settings.json.

1 {
2 "workbench.colorTheme": "Visual Studio Dark",
3 "code-runner.executorMap": {
4 "python": "python3 $workspaceRoot/main.py",
5 },
6 "code-runner.clearPreviousOutput": true,
7 "code-runner.saveAllFilesBeforeRun": true,
8 "git.autofetch": true,
9 }
First App
To test the environment we now create a simple app.

basic.py

1 import sys
2 from PyQt5.QtWidgets import QApplication, QWidget
3

4
5
app = QApplication(sys.argv)
6 w = QWidget()
7 w.resize(250, 150)
8 w.setWindowTitle('Simple')
9 w.show()
10 app.exec()

In this case, because the python file is not named main.py we will run
it using the terminal inside of VSCode.

user@machine:/path$ python3 basic.py


Here we create an app, instantiate a widget, set the widgets size, set
a window title, make the widget visible and execute the main loop.
In this main loop the app is polling for events like mouse and keyboard
send them to the window and the window is reacting on them. In this
case it will only react on resize, move and the close event when the
user clicks the green close button on the upper right corner of the
window where the main loop is exited.

Summary
After setting up the development environment we now have created
our first Qt app at least for the desktop.
Part II - Basics
Many Different Approaches
Because of the fact that Python has been introduced in 1991,
meanwhile there are a few user interface approaches to develop
Python applications. There is Tkinter which is a bridge to TK. Tkinter is
been delivered with Python. There is Kivy which has got a nice
approach using a special pythonic language to declare the user
interface. There is BeeWare which compiles Python to Java-ByteCode
which then can be deployed on an Android device. There is Enaml
Native which act like Pythons answer to React Native and there are a
few approaches to bridge to Qt. Qt is pronounced 'Cute'. These
approaches are PySide, which bridges to Qt4. There is PyQt which
also bridges to Qt4, PySide2 which bridges to Qt5 and last but not
least PyQt5 which we are talking about in this book. I am not going to
talk about the pros and cons of all these possibilities in this book.
Instead I am focusing on solutions. To use PyQt5 is a personal
decision after working a few years with Qt5. Qt5 and PyQt5 are
available under an open source license so you might use these
frameworks for free as long as you plan to create open source
programs. If you are going to create commercial software you have to
purchase licenses for both frameworks. Even when we are using Qt5
we have got two options to develop applications. First option is
QtWidgets which has been introduced to create platform independent
desktops applications and QtQuick which uses a declarative way to
implement user interfaces using QML (Qt Markup Language).
Because at the moment QtQuick lacks of a tree view and a table view
implementation I guess it has been primarily developed to satisfy
mobile app development. QtQuick has also implemented behaviours
and transitions which you normally only see on mobile platforms. If
you have got design background then the QML approach could be
your best choice because you don't really have to write code that
much and when you like to write imperative code then QtWidgets
might be your way.
Hello World (QtWidgets)
Now I will show you how a very basic QtWidgets app will look like.

QWidget/Basics/main.py

1 import sys
2 from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
3 from PyQt5.QtCore import Qt
4

5
6
class MainWindow(QMainWindow):
7 def __init__(self):
8 QMainWindow.__init__(self)
9 self.setWindowTitle("Qt Demo")
10 label = QLabel("Hello World")
11 label.setAlignment(Qt.AlignCenter)
12 self.setCentralWidget(label)
13
14
if __name__ == "__main__":
15 app = QApplication(sys.argv)
16 win = MainWindow()
17 win.show()
18 sys.exit(app.exec())

The sample is simply self explanatory. We are creating an application


object. Instantiate a window. Make the window visible and execute the
app main loop.
In the window there is just a label widget which will be set as the
central widget.
In bigger projects it would be better to create a python file for each
class so that the class MainWindow will become an own file called
mainwindow.py.

Hello World (QtQuick)


The hello world app for the QtQuick part will be made out of two file.
First the main.py where the qml file will be loaded and the main loop
will be started and a second file named view.qml where the user
interface is been declared.

QtQuick/Basics/main.py

1 import sys
2 from PyQt5.QtGui import QGuiApplication
3 from PyQt5.QtQml import QQmlApplicationEngine
4

5
6
if __name__ == "__main__":
7 app = QGuiApplication(sys.argv)
8 engine = QQmlApplicationEngine("view.qml")
9 if not engine.rootObjects():
10 sys.exit(-1)
11 sys.exit(app.exec())

QtQuick/Basics/view.qml

1 import QtQuick 2.0


2 import QtQuick.Controls 2.5
3

4 ApplicationWindow
5 {
6 visible: true
7

8 Text
9 {
10 anchors.horizontalCenter: parent.horizontalCenter
11 anchors.verticalCenter: parent.verticalCenter
12 text: "Hello World"
13 }
14 }

In order to start the app we have to cd into the Basics directory and
start it like this:

user@machine:/path$ python3 main.py


This app is similar to the QtWidgets version except the fact that we
use a QGuiApplication instead of a QApplication. And also a
QQmlApplicationEngine is responsible to load the user interface.

Combining QWidget and QML


A third possibility is to combine both QWidget and QML using the
QQuickView inside a QWidget application.

Combo/main.py

1 import sys
2 from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel,
QVBoxLayout, QWidget
3 from PyQt5.QtCore import Qt, QUrl
4 from PyQt5.QtQuick import QQuickView
5

6
7
class MainWindow(QMainWindow):
8 def __init__(self):
9 QMainWindow.__init__(self)
10 self.setWindowTitle("Qt Combo Demo")
11 widget= QWidget()
12 layout = QVBoxLayout()
13 view = QQuickView()
14 container = QWidget.createWindowContainer(view, self)
15 container.setMinimumSize(200, 200)
16 container.setMaximumSize(200, 200)
17 view.setSource(QUrl("view.qml"))
18 label = QLabel("Hello World")
19 label.setAlignment(Qt.AlignCenter)
20 layout.addWidget(label)
21 layout.addWidget(container)
22 widget.setLayout(layout)
23 self.setCentralWidget(widget)
24

25
26
if __name__ == "__main__":
27 app = QApplication(sys.argv)
28 win = MainWindow()
29 win.show()
30 sys.exit(app.exec())

Combo/view.qml

1 import QtQuick 2.1


2

3 Rectangle
4 {
5 id: rectangle
6 color: "red"
7 width: 200
8 height: 200
9

10 Text
11 {
12 id: text
13 text: "This is QML code"
14 font.pointSize: 14
15 anchors.centerIn: parent
16 PropertyAnimation
17 {
18 id: animation
19 target: text
20 property: "rotation"
21 from: 0; to: 360; duration: 5000
22 loops: Animation.Infinite
23 }
24 }
25 MouseArea
26 {
27 anchors.fill: parent
28 onClicked: animation.paused ? animation.resume() :
animation.pause()
29 }
30 Component.onCompleted: animation.start()
31 }

Be aware that you should not use many QQuickViews inside a


QWidget window because of performance issues.

Summary
We have seen three approaches to build GUI applications using Qt5.
The QWidgets approach is mostly used to create desktop
applications. The QML approach is mostly used to create mobile
applications and the combination can be used to create desktop
applications where we use QML to render a part of thes desktop
experience.
Part III - QML
QML Syntax
The QML syntax has been invented to declare user interfaces with an
easy to read and easy to write new syntax, based on the ideas of
XAML, Json and Javascript.
It combines the best of all of these languages.

In QML the userinterface is declared as a hirachy tree. Each item is


positioned inside another item. The events triggert by a control, when
teh user clicks a button for example, can be coded in a javascript like
language.

To declare the item tree we use the item name followed by an open
bracket and a close bracket.

Rectangle { }

To set the value of a property you write the name of the property
followed by a colon ":" and then the value.

name: value

To declare numeric value you just put the numeric digit behind the
colon.

width: 200

If you have got a text, then the value if put between quotations marks.
" or '.

text: "The quick brown fox"


If you are using colors you put them also between quotation marks,
leaded by a hash.

color: "#FF00FF"

Here the red part of the color is set to hex FF (255). Green is set to 0
and blue is set to FF (255).

You can also set the opacity using an additional hex value.

color: "#FF00FFEE"

Here the opacity has been set to EE (238).

It is also possible to bind values of items to values of other items using


javascript.

text: "The parent is " + parent.width + " pixels wide"

Lets assume the parent is the application window which I have set to a
width of 350 pixels, then the text will say: The parent is 350 pixels
wide
If I would resize the window then this value is updated with every
mousemove event to show the new width.

Id

A special value in QML is called id.

1 ApplicationWindow
2 {
3 id: root
4 Text
5 {
6 text: "The parent is " + root.width + " pixels wide"
7 }
8 }
The id is a readonly value which can only be declared once. It is used
to reference items. A good practice is to name the root item with the
id: root, so that it's similar to all QML objects.

Child elements inherits the coordinate sytem from the parent. So the x
and y coordinates are always relative to the parent.

Every QML needs to have exactly one root element. So there can only
be one ApplicationWindow for example.

Child Objects

Any object declaration can define child objects through nested object
declarations. In this way, any object declaration implicitly declares an
object tree that may contain any number of child objects.

1 Rectangle
2 {
3 color: "red"
4

5 MouseArea
6 {
7 anchors.fill: parent
8 }
9 }

Comments

Comments can be made using // for a single line or /* */ for multiple


lines, just like in C, C++ and Javascript.

Elements can be nested. So a parent element can have multiple child


elements.

Import Statements

1 import Namespace VersionMajor.VersionMinor


2 import Namespace VersionMajor.VersionMinor as SingletonIdentifier
3 import "directory"
4 import "file.js" as ScriptIdentifier

With the import statement you import a specific version of a QML


module.

import QtQuick 2.5

The major version points to the QtQuick version you intent to use. And
the minor version stands for the Qt version. So the above sample is
importing QtQuick 2 and a Qt version 5.*.
So the version 2.12 to import points to QtQuick 2 and Qt 5.12.

Properties
You can declare properties using the property qualifier followed by the
type, the name and an optional initial value. The syntax is property
<type> <name> : <value>

property int clickCount: 0

Another possibilty is to use the alias keyword to forward a property of


an object or the object itself to the outer scope. The syntax is property
alias <name>: <reference>

property alias text: lable.text

A property alias does not need a type. It used the type of the
referenced property.

Property Binding
One of the most importand features of QML is the property binding. A
value of a property can be set via a constant, an expression or via
binding to another property.
1 Rectangle
2 {
3 width: 200; height: 200
4

5 Rectangle
6 {
7 width: 100
8 height: parent.height
9 color: "red"
10 }
11 }

In the above case the height has been bound to the height of the
parent object. If we change the height of the parent, then the height of
the inner rect will be adjusted automatically.

Signals
Receiving signals and signal handlers

To receive a notification when a signal is emitted for an object, the


object definition should declare a signal handler named on, where
Signal is the name of the signal, with the first letter capitalized. The
signal handler should contain the JavaScript code to be executed
when the signal handler is invoked.

1 Button
2 {
3 id: button
4 anchors.bottom: parent.bottom
5 anchors.horizontalCenter: parent.horizontalCenter
6 text: "Change color!"
7

8 onClicked:
9 {
10 root.color = Qt.rgba(Math.random(), Math.random(),
Math.random(), 1);
11 }
12 }
In the above case the Button has got a signal with the name clicked
and we create a signal handler with the corresponding name
onClicked.

Property change signal handlers

A signal is emitted when the value of a QML property changes. This


type of signal is a property change signal and signal handlers for
these signals are written in the form onChanged, where
PropertyName is the name of the property, with the first letter
capitalized.

1 Rectangle
2 {
3 id: rect
4 width: 100; height: 100
5 color: "#C0C0C0"
6

7 TapHandler
8 {
9 onPressedChanged: console.log("pressed changed to ", pressed)
10 }
11 }

For example, the TapHandler of a Rectangle has got a pressed


property. To receive a notification whenever this property changes,
write a signal handler named onPressedChanged like in the above
sample.

Connections

In some cases we want to access a signal outside of an object.


Therefore we use the Connections type.

1 Connections
2 {
3 target: button
4 onClicked:
5 {
6 rect.color = Qt.rgba(Math.random(), Math.random(),
Math.random(), 1);
7 }
8 }

Attached signal handlers

An attached signal handler receives a signal from an attached type.

1 Component.onCompleted:
2 {
3 console.log("The windows title is", title)
4 }

In the above case the onCompleted event is fired, when the


ApplicationWindow has been loaded.

Signals for a custom QML type

The syntax for defining a new signal is:


signal [([ [, ...]])]
A signal is emitted by invoking the signal as a method.

1 // SquareButton.qml
2 import QtQuick 2.12
3

4 Rectangle
5 {
6 id: root
7 signal activated(real xPosition, real yPosition)
8 property point mouseXY
9 property int side: 100
10 width: side; height: side
11

12 TapHandler
13 {
14 id: handler
15 onTapped: root.activated(mouseXY.x, mouseXY.y)
16 onPressedChanged: mouseXY = handler.point.position
17 }
18 }
The Rectangle object has an activated signal, which is emitted
whenever the TapHandler is tapped.

To use this signal you declare an onActivated handler in the code that
uses the SqareButton.

1 SqareButton
2 {
3 color: "#345462"
4 x: 200
5 y: 0
6 onActivated: console.log("Activated at " + xPosition + "," +
yPosition)
7 }

Functions
To declare a JavaScript function you are using the folowing syntax

function <name> ( <parameters> ) { ... }

In the next sample we have got the Text with the id txt. In the same
parent we have go a MouseArea which is used just a get a click event
when the user click in it with the mouse or when the user touches this
area on a mobile.

anchor.fill: parent

This gives the MouseArea the same size and position as the parent.

1 property int clickCount: 0


2

3 Text
4 {
5 id: txt
6 anchors.horizontalCenter: parent.horizontalCenter
7 anchors.verticalCenter: parent.verticalCenter
8 text: "Right - Bottom "
9 }
10
11 MouseArea
12 {
13 anchors.fill: parent
14 onClicked:
15 {
16 increment()
17 txt.text = "clicked " + clickCount + " times"
18 }
19
20
function increment()
21 {
22 clickCount++
23 }
24 }

The MouseArea has got a Clicked event, which we are coding with the
"on" prefix.
If you have more than one line of code you have to use brackets.
In this event we are calling the function increment.
The property clickCount we declare on top level. It's a god habit to
declare all variables in the top of the root element.

Interaction between QML and Python


You may code almost everything in QML (Javascript), but sometimes
you need to do business logic in Python. Therefore I will show you a
possible bridge.
We are creating a class derived from QObject and give an instance of
this class to QML via context property.

1 import sys
2 import os
3 from PyQt5.QtGui import QGuiApplication
4 from PyQt5.QtQml import QQmlApplicationEngine
5 from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
6

7
8
class Bridge(QObject):
9 textChanged = pyqtSignal()
10
11
def __init__(self, parent=None):
12 QObject.__init__(self, parent)
13 self._root = None
14 self._cwd = os.getcwd()
15
16
def setRoot(self, root):
17 self._root = root
18
19
@pyqtProperty(str, notify=textChanged)
20 def cwd(self):
21 return self._cwd
22
23
@pyqtSlot("QString")
24 def message(self, value):
25 print(value + " from QML")
26 self._root.updateMessage(value + " from Python")
27

28
29
if __name__ == "__main__":
30 bridge = Bridge()
31 app = QGuiApplication(sys.argv)
32 engine = QQmlApplicationEngine()
33 engine.rootContext().setContextProperty("bridge", bridge)
34 engine.load("view.qml")
35 roots = engine.rootObjects()
36 bridge.setRoot(roots[0])
37 if not roots:
38 sys.exit(-1)
39 sys.exit(app.exec())

In QML you can use the object bridge to communicate with Python.

1 import QtQuick 2.0


2 import QtQuick.Controls 2.5
3
4
ApplicationWindow {
5 width: 640
6 height: 480
7 visible: true
8 title: "QML Demo"
9
10
function updateMessage(text)
11 {
12 txt.text = text
13 }
14
15
Text {
16 id: txt
17 anchors.horizontalCenter: parent.horizontalCenter
18 anchors.verticalCenter: parent.verticalCenter
19 text: "cwd: " + bridge.cwd
20 }
21 MouseArea {
22 anchors.fill: parent
23 onClicked:
24 {
25 bridge.message("click")
26 }
27 }
28 }

bridge.cwd is using the property declared in Python to get the current


working directory.
And with bridge.message we are sending data to a Python slot.
On the Python side we are able to call a Javascript function using the
root object.

self._root.updateMessage(value + " from Python")

Screen size
To place items on the surface we have to position them.
When you are comming from a windows environment like WinForms,
then you know how to position item using their x and y coordinates.
This was fine for windows, where the screensize was kind of known
back in the years.
We only had screen sizes like 640 x 480 or 1024 x 768. Where you
tried to support the smallest screen resolution when designing a
dialog.
On a mobil phone we have hundreds of different screen resolutions
and form factors, so we have to change our thinking how to design a
dialog.
Also we do not create dialogs anymore.
Normally we create full screen pages.
When you are developing on a desktop computer you have to
simulate the form factor of a mobile phone.
Because of the fact that my mobile phone has a resolution of 700 *
1200 and my desktop has 1366 * 768, I have to find a screen size to
simulate the portrait mode of my phone on my desktop. So I am
setting the ApplicationWindow to the size 350 * 600, so that the
window fits on my screen and mimics the portrait mode of a mobile
phone.

1 ApplicationWindow #
2 {
3 id: applicationWindow
4 width: 350
5 height: 600
6 visible: true
7 title: "Anchors Demo"

You should find another but similar solution for your environment.

So when we would use the direct positioning like we did on windows,


where we used the x and y coordinates to position an item, we should
use a different solution for the phone, because we don't know the
screen resolution of the target platform.

Positioning Items
To position an item in QML we are using anchors.
You can still use the x and y coordinates, but it only makes sense, if
the item is anchored to the upper left. You can anchor an item to the
top, bottom, left and to the right. Additionally you can also anchor an
item to the horizontalCenter and to the verticalCenter.

1 Rectangle
2 {
3 id: rectangle
4 width: 100
5 height: 100
6 color: "#75507b"
7 anchors.horizontalCenter: parent.horizontalCenter
8 anchors.verticalCenter: parent.verticalCenter
9 }

In the above sample the center of the rectangle is bound to the center
of the parent item, which is the application window or it could be
another item.

If you want to anchor an item to the lower right then you positioning it
as follows.

1 Rectangle
2 {
3 width: 100
4 height: 100
5 color: "#73d216"
6 anchors.bottom: parent.bottom
7 anchors.bottomMargin: 20
8 anchors.right: parent.right
9 anchors.rightMargin: 20
10 }

The margin is the distance between the item am the parents site.

In the source code to this book you will find a sample with all anchors
under QtQuick/Anchors.

Controls
The most used control on a mobile phone might be the listbox.
Here is a sample which shows how to use it with a static model.

1 import QtQuick 2.0


2 import QtQuick.Controls 2.5
3 import QtQuick.Layouts 1.1
4
5
ApplicationWindow {
6 width: 640
7 height: 480
8 visible: true
9 title: "QML Listview Demo"
10
11 ListView
12 {
13 clip: true
14 anchors.fill: parent
15 anchors.margins: 5
16 spacing: 5
17
18
delegate: listDelegate
19

20 Component
21 {
22 id: listDelegate
23

24 RowLayout
25 {
26 anchors.left: parent.left
27 anchors.right: parent.right
28 anchors.margins: 10
29 spacing: 10
30
31
CheckBox {}
32 Label { text: itemType; color: "#888"; font.italic: true
}
33 Label { text: itemName; Layout.fillWidth: true }
34 Label { text: itemPath }
35 ComboBox { model: itemVersions; Layout.preferredWidth: 90
}
36 }
37 }
38
39
model: ListModel
40 {
41 ListElement
42 {
43 itemType: "asset"
44 itemName: "First entry"
45 itemPath: "/documents/fe.md"
46 itemVersions: []
47 }
48 ListElement
49 {
50 itemType: "asset"
51 itemName: "Second entry"
52 itemPath: "/documents/se.md"
53 itemVersions: []
54 }
55 ListElement
56 {
57 itemType: "asset"
58 itemName: "Third entry"
59 itemPath: "/documents/te.md"
60 itemVersions: []
61 }
62 }
63 }
64 }

To render the list we design a component which is used as a delegate.


The model which delivers the data we use declarative here in this
sample, to make it easier to understand.

So next we will use a model build in Python.

1 import sys
2 from PyQt5.QtGui import QGuiApplication
3 from PyQt5.QtQml import QQmlApplicationEngine
4 from PyQt5.QtCore import Qt, QObject, pyqtProperty, pyqtSignal, pyqtSlot,
QAbstractListModel, QModelIndex
5

6
7
class Model(QAbstractListModel):
8 def __init__(self, items, parent=None):
9 super(Model, self).__init__(parent)
10 self._items = items
11
12
def rowCount(self, parent=None):
13 return len(self._items)
14
15
def data(self, index, role=None):
16 role = role or QModelIndex()
17
18
if role == Qt.UserRole + 0:
19 return self._items[index.row()]["type"]
20
21
if role == Qt.UserRole + 1:
22 return self._items[index.row()]["name"]
23
24
if role == Qt.UserRole + 2:
25 return self._items[index.row()]["path"]
26
27
if role == Qt.UserRole + 3:
28 return self._items[index.row()]["versions"]
29
30
def roleNames(self):
31 return {
32 Qt.UserRole + 0: b"itemType",
33 Qt.UserRole + 1: b"itemName",
34 Qt.UserRole + 2: b"itemPath",
35 Qt.UserRole + 3: b"itemVersions",
36 }
37

38
39
if __name__ == "__main__":
40 items = [
41 {
42 "type": "asset",
43 "name": "shapes",
44 "path": "c:/users/Roy/Desktop/shapes.ma",
45 "versions": ["v001", "v002", "v003"]
46 },
47 {
48 "type": "asset",
49 "name": "shapes1",
50 "path": "c:/users/Roy/Desktop/shapes.ma",
51 "versions": ["v001", "v002", "v003", "v004"]
52 },
53 {
54 "type": "asset",
55 "name": "shapes2",
56 "path": "c:/users/Roy/Desktop/shapes.ma",
57 "versions": ["v001", "v002", "v003"]
58 },
59 ]
60 model = Model(items)
61
62
app = QGuiApplication(sys.argv)
63 engine = QQmlApplicationEngine()
64 engine.rootContext().setContextProperty("mymodel", model)
65 engine.load("view.qml")
66 roots = engine.rootObjects()
67 if not roots:
68 sys.exit(-1)
69 sys.exit(app.exec())

In the view we just have to change the model.

model: mymodel
The model object has now been set as a context property.

Summary
We have seen the basics of QML. We are now able to create simple
apps using QML and Python.
Part IV - Deploment
Deploying a dynamic app to Android
Now we are going to build an APK (Android Package). Therefore we
first have to install a few other components.

Install pyqtdeploy 2.4


pyqtdeploy is a tool made by the same company who also created
PyQt5.
You can install pyqtdeploy via pip.

user@machine:/path$ pip3 install pyqtdeploy

Install Java JDK 8


You should be able to find all you need to install Java JDK 8 here:
https://docs.oracle.com/javase/8/docs/technotes/guides/install/install_overview.htm

Just to mention I had problem to build the package with Java JDK 10
installed.

Install Android SDK


To install the Android SDK go to https://developer.android.com/studio
scroll down to Command line tools only and download the file for your
platform.
Then unzip the file somewhere on your computer. After unzipping go
to the Sdk/tools/bin directory and run the sdkmanager to install all
necessary tools:

user@machine:/path$ ./sdkmanager "platform-tools" "platforms;android-28"


Install Android NDK
To install the Android NDK go to
https://developer.android.com/ndk/downloads scroll down to Older
Versions, click the link NDK Archives, agree to the terms and
download Android NDK, Revision 19c for your platform (Unfortuneatly
version 20 is not working for us yet).
After downloading unzip all the file into the SDK directory as ndk-
bundle. Your Android directory should look like the following:

Install Qt
Now we also need Qt. You can download it here:
https://www.qt.io/download. You should install the following
components.
You need Desktop to test your apps on the desktop.
You need Android x86 to test yours apps on a simulator.
You need Android ARM64 to test on a 64 bit device and
you need Android ARMv7 to test on a 32 bit device.

Download Source packages


The following packages should only be downloaded and not extracted.

openssl-1.0.2s.tar.gz from https://www.openssl.org/source/


PyQt3D_gpl-5.12.tar.gz from
https://www.riverbankcomputing.com/software/pyqt3d/download
PyQt5_gpl-5.12.1.tar.gz from
https://www.riverbankcomputing.com/software/pyqt/download5
PyQtChart_gpl-5.12.tar.gz from
https://www.riverbankcomputing.com/software/pyqtchart/download
PyQtDataVisualization_gpl-5.12.tar.gz from
https://www.riverbankcomputing.com/software/pyqtdatavisualization/download
PyQtPurchasing_gpl-5.12.tar.gz from
https://www.riverbankcomputing.com/software/pyqtpurchasing/download
Python-3.7.2.tar.xz from
https://www.python.org/downloads/source/
QScintilla_gpl-2.11.1.tar.gz from
https://www.riverbankcomputing.com/software/qscintilla/download
qt-everywhere-src-5.12.2.tar.xz from
http://download.qt.io/official_releases/qt/5.12/5.12.2/single/qt-
everywhere-src-5.12.2.tar.xz.mirrorlist
sip-4.19.15.tar.gz from
https://www.riverbankcomputing.com/software/sip/download
zlib-1.2.11.tar.gz from https://zlib.net/

Create the build script


You should change the paths in the following script to your needs.

Deploy/build.sh
1 export ANDROID_NDK_ROOT=/media/art/data/Android/Sdk/ndk-bundle
2 export ANDROID_NDK_PLATFORM=android-28
3 export ANDROID_SDK_ROOT=/media/art/data/Android/Sdk
4 pyrcc5 main.qrc -o lib/main_rc.py
5 python3.7 build.py --target android-32 --installed-qt-dir
/media/art/data/Qt/5.12.2 --no-sysroot --verbose --source-dir
./../../DeployAndroid/external-sources

external-sources points to the directory for the just downloaded


packages.
If you want to install the app to an Android device with 64 bit, then you
should change the target to android-64 .
--installed-qt-dir points to the directory where Qt has been installed.

Create the build.py script


The build.py I have got from here:
https://pypi.org/project/pyqtdeploy/#files from the Demo project. I have
just added these lines of code:

Deploy/build.py

1 ...
2 run(['pyqtdeploy-build', '--target', target, '--sysroot', sysroot_dir, '--
build-dir', build_dir, 'demo.pdy'])
3
4
# copy the main.qml to a directory where androiddeployqt will find it to add
required libraries based on the import statements
5 cp = "cp " + os.path.join(dir_path, "view.qml") + " " +
os.path.join(dir_path, build_dir)
6 run([cp])
7 # append the ANDROID_PACKAGE to the .pro file
8 with open(os.path.join(dir_path, build_dir, "main.pro"), "a") as fp:
9 fp.write("\ncontains(ANDROID_TARGET_ARCH, armeabi-v7a)
{\nANDROID_PACKAGE_SOURCE_DIR = " + os.path.join(dir_path, "android") + "\n}")
10
11
os.chdir(build_dir)
12 ...

It is necessary that the androiddeployqt app will find a QML file in this
directory. It scans all needed QML files for import statements to
include the needed shared libraries into the APK.
androiddeployqt will be called by pyqtdeploy.

You can find the complete build.py and all other source files in the
github repo: https://github.com/Artanidos/DevAndroidPythonBook

With the ANDROIDPACKAGESOURCE_DIR we specify that we have


got Android specific files like the AndroidManifest.xml and the icons in
this specific folder.
Also an app theme is included there. This theme only has one
important thing Theme.DeviceDefault.Light.NoActionBar. This is
needed to make the splash screen look not so ugly.
This is the default splash screen. With this little change the screen is
just white.

Create the resource file


The resource file will contain our QML file as a python resource.
To build the resource run the following.

user@machine:/path$ pyrcc5 main.qrc -o lib/main_rc.py

This is needed to find the QML later on the device. After these
changes we have to change the main.py as follows:
Deploy/main.py

1 import sys
2 import os
3 import lib.main_rc
4 from PyQt5.QtGui import QGuiApplication
5 from PyQt5.QtQml import QQmlApplicationEngine
6
7
if __name__ == "__main__":
8 sys_argv = sys.argv
9 sys_argv += ['--style', 'material']
10 app = QGuiApplication(sys.argv)
11 view = "/storage/emulated/0/view.qml"
12 if os.path.exists(view):
13 # we are trying to load the view dynamically from the root of the
storage
14 engine = QQmlApplicationEngine(view)
15 if not engine.rootObjects():
16 sys.exit(-1)
17 else:
18 # if the attempt to load the local file fails, we load the fallback
19 engine = QQmlApplicationEngine(":/view.qml")
20 if not engine.rootObjects():
21 sys.exit(-1)
22 sys.exit(app.exec())

In this app we are looking for the file viem.qml, if it's stored on the
mobile phone root. This file we will produce later. It can be edited on
the phone, so you don't have to recompile the whole app over and
over.

With import lib.main_r we add the resource file.


And with the double point in :/view.qml we will tell Qt to load this file
from a resource.

Create a project file


The project file has the name demo.pdy and is needed for pyqtdeploy
to be able to freeze all necessary packages.

Deploy/demo.pdy
1 <?xml version='1.0' encoding='utf-8'?>
2 <Project usingdefaultlocations="1" version="7">
3 <Python major="3" minor="7" patch="2" platformpython="" />
4 <Application entrypoint="" isbundle="0" isconsole="0" ispyqt5="1" name=""
script="main.py" syspath="">
5 <Package name="lib">
6 <PackageContent included="1" isdirectory="0" name="__init__.py" />
7 <PackageContent included="1" isdirectory="0" name="main_rc.py" />
8 <Exclude name="*.py" />
9 <Exclude name="*.qml" />
10 <Exclude name="*.sh" />
11 <Exclude name="*.pdy" />
12 <Exclude name="*.json" />
13 <Exclude name="*.qrc" />
14 <Exclude name="build-android-32" />
15 <Exclude name="sysroot-android-32" />
16 </Package>
17 </Application>
18 <PyQtModule name="QtWidgets" />
19 <PyQtModule name="QtNetwork" />
20 <PyQtModule name="QtAndroidExtras" />
21 <PyQtModule name="QtSvg" />
22 <PyQtModule name="QtQuick" />
23 <PyQtModule name="QtQml" />
24 <PyQtModule name="Qt" />
25 <PyQtModule name="QtQuickWidgets" />
26 <PyQtModule name="QtSensors" />
27 <PyQtModule name="QtBluetooth" />
28 <StdlibModule name="http.server" />
29 <StdlibModule name="http" />
30 <StdlibModule name="ssl" />
31 <StdlibModule name="sysconfig" />
32 <StdlibModule name="zlib" />
33 <StdlibModule name="importlib.resources" />
34 <StdlibModule name="os" />
35 <StdlibModule name="marshal" />
36 <StdlibModule name="imp" />
37 <StdlibModule name="logging" />
38 <StdlibModule name="logging.config" />
39 <StdlibModule name="logging.handlers" />
40 <StdlibModule name="contextlib" />
41 <StdlibModule name="urllib" />
42 <StdlibModule name="urllib.request" />
43 <StdlibModule name="traceback" />
44 <ExternalLib defines="" includepath="" libs="-lz" name="zlib"
target="android" />
45 </Project>

One important thing here is the fact that we need a directory for all
other python files to be included into the APK. We are using lib here.
Inside this directory should be an empty file named __init__.py.
Then all needed Qt packages are defined here. (We don't need all of
them but maybe later).
And also all needed standard python libraries are listed here. This file
can be produced using this command in the terminal:

user@machine:/path$ pyqtdeploy

Create sysroot.json
This file hold all properties necessary to create a sysroot directory
where some tools will be compiled.

Deploy/sysroot.json

1 {
2 "Description": "The sysroot for the DynPy application.",
3
4
"android|macos|win#openssl":
5 {
6 "android#source": "openssl-1.0.2s.tar.gz",
7 "macos|win#source": "openssl-1.1.0j.tar.gz",
8 "win#no_asm": true
9 },
10
11
"linux|macos|win#zlib":
12 {
13 "source": "zlib-1.2.11.tar.gz",
14 "static_msvc_runtime": true
15 },
16
17
"qt5":
18 {
19 "android-32#qt_dir": "android_armv7",
20 "android-64#qt_dir": "android_arm64_v8a",
21
22
"linux|macos|win#source": "qt-everywhere-src-5.12.2.tar.xz",
23 "edition": "opensource",
24
25
"android|linux#ssl": "openssl-runtime",
26 "ios#ssl": "securetransport",
27 "macos|win#ssl": "openssl-linked",
28
29
"configure_options": [
30 "-opengl", "desktop", "-no-dbus", "-qt-pcre"
31 ],
32 "skip": [
33 "qtactiveqt", "qtconnectivity", "qtdoc", "qtgamepad",
34 "qtlocation", "qtmultimedia", "qtnetworkauth",
35 "qtremoteobjects",
36 "qtscript", "qtscxml", "qtserialbus",
37 "qtserialport", "qtspeech", "qttools",
38 "qttranslations", "qtwayland", "qtwebchannel",
"qtwebengine",
39 "qtwebsockets", "qtwebview", "qtxmlpatterns"
40 ],
41
42
"static_msvc_runtime": true
43 },
44
45
"python":
46 {
47 "build_host_from_source": false,
48 "build_target_from_source": true,
49 "source": "Python-3.7.2.tar.xz"
50 },
51
52
"sip":
53 {
54 "module_name": "PyQt5.sip",
55 "source": "sip-4.19.15.tar.gz"
56 },
57
58
"pyqt5":
59 {
60 "android#disabled_features": [
61 "PyQt_Desktop_OpenGL", "PyQt_Printer",
"PyQt_PrintDialog",
62 "PyQt_PrintPreviewDialog", "PyQt_PrintPreviewWidget"
63 ],
64 "android#modules": [
65 "QtQuick", "QtCore", "QtGui", "QtNetwork",
"QtPrintSupport", "QtWidgets",
66 "QtAndroidExtras", "QtQuickWidgets", "QtSvg",
"QtBluetooth", "QtNetwork", "QtSensors",
67 "QtQml"
68 ],
69
70
"source": "PyQt5_*-5.12.1.tar.gz"
71 }
72 }

Build the APK


If all went well we are now able to build the APK and deploy it to a
device. Therefore we run ./build.s in the terminal. Make sure that
build.sh is executable. This first will create a sysroot directory with
tools and libraries. If you are going to build the APK a second time you
can skip building the sysroot with this option in the build.sh:

--no-sysroot

Install APK to device


When you have successfully build the APK you are now able to install
it to your device.
First of all make sure the developer-mode is switched on on your
device. If not you have to switch it on first.
Open your device settings app. Look for "About you phone" and
search for the "Build Number". Then you have to tap on it seven times.
You should get a message that the developer options are now turned
on. Connect your device to your computer using a USB cable.
Now open the developer option in the settings app and look for USB-
Configuration. Normally it is set to "Just load". Change it now to media
transfer protocol (MTP).
Then look for USB-Debugging and switch it to on.
This done you are now able to find your device with the adb
command:

1 user@machine:/path$ adb devices


2 List of devices attached
3 * daemon not running; starting now at tcp:5037
4 * daemon started successfully
5 5WH6R19329010194 device
If your device is listed copy the string to the left and paste it into the
terminal:

user@machine:/path$ adb -s 5WH6R19329010194 install


/home/art/Sourcecode/Python/Book/Deploy/build-android-
32/dynpy/build/outputs/apk/debug/dynpy-debug.apk

Also copy the APK path which will be displayed after the build process
has finished.

I have put the whole command into the deploy.sh file to use it later.

Now you can try to start the app. It should show up with the text
DynPy in the middle.
Now you open the settings app on your phone and enable the storage
for DynPy.
Then the app can open file here: /storage/emulated/0, which emulates
the root on the phone.
Now we are creating a new file with the name view.qml in this
directory on your phone and restart the app.

1 import QtQuick 2.5


2 import QtQuick.Controls 2.0
3

4 ApplicationWindow
5 {
6 visible: true
7

8 Text
9 {
10 anchors.centerIn: parent
11 text: "DynPy"
12 }
13 }

If everything went well, the app shall now display "DynPy".

This was you are able to develop the app further directly on the
phone, without the need to recompile everything.
Summary
Hopefully we have deployed the DynPy app to our device. To find out
how to build and deploy to Android took me several days.
If you are running into trouble here don't hesitate to contact me.
Maybe I can help you out and publish our experience into the next
version of this book so that other people are not running into trouble.
Afterwords
I am happy that you have read so far. I hope this book could help you
to learn some more possibilities as a software developer. I wish you
good luck on your way. If you like this book then I would appreciate
when you could write a small review to help other people to also find
this book.

About The Author

Olaf Art Ananda, born 1963 in Hamburg, Germany has been a


software developer for over 30 years now. He started with C, learned
Assembler to speed up C programs and after trying out most popular
programming languages like Java, C# and Objective-C he came back
to C/C++ and started to develop desktop appĺications using Qt5 in
2016. Qt5 was the ideal platform for him to express his skills he has
learned studying Human Computer Interaction Design in 2013. After a
first try to use Python to develop plug-ins for his programs he needed
another two years to really get to know Python. Today he enjoys the
simplicity of this language to write apps in a very short time compared
to C++. Olaf has worked for several top 500 companies like Dupont,
Dresdner Bank, Commerzbank and Zürcher Kantonalbank to name a
few. After his burnout and a near death experience he decided to quit
working for profit. Since 2016 he is writing open source software like
the AnimationMaker, the FlatSiteBuilder and the EbookCreator. He
also has written a book about his life and Tantra. Since 2016 he is
living in his mobile home in the middle of Berlin and he is also playing
percussion on the streets. To write code he is going to a public library.
That's an easy living.

You might also like