(FreeCourseWeb - Com) Python GUI Develop Android Applications Using Python, QT and PyQt5
(FreeCourseWeb - Com) Python GUI Develop Android Applications Using Python, QT and PyQt5
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.
Italic
Constant width
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 }
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:
artanidos@gmail.com
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 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.
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.
Python
Coderunner
With coderunner you are able to start your app with just on click.
QML
VSCode Settings
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.
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())
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
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:
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
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 }
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.
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 '.
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"
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
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
Import Statements
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>
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
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.
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 }
Connections
1 Connections
2 {
3 target: button
4 onClicked:
5 {
6 rect.color = Qt.rgba(Math.random(), Math.random(),
Math.random(), 1);
7 }
8 }
1 Component.onCompleted:
2 {
3 console.log("The windows title is", title)
4 }
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
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.
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.
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.
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.
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.
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 }
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())
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.
Just to mention I had problem to build the package with Java JDK 10
installed.
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.
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
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
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.
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 }
--no-sysroot
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.
4 ApplicationWindow
5 {
6 visible: true
7
8 Text
9 {
10 anchors.centerIn: parent
11 text: "DynPy"
12 }
13 }
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.