Releasing to Android and iOS - Creating Apps in Kivy (2014)

Creating Apps in Kivy (2014)

Chapter 9. Releasing to Android and iOS

Now that you have a functional weather application, it’s time to actually deploy it to your mobile device. In this chapter we’ll discuss how to deploy to Android, which can be done from a Linux or Mac OS machine, and to iOS if both your host and device are Apple products. Kivy has a custom-built deployment tool called Buildozer. This tool is both a blessing and a curse. On the positive side, it works very hard (and very well) to take care of all the crazy dependencies that are required to deploy to these disparate platforms, and it has the second-coolest name in the packaging industry (Arch Linux’s pacman package manager is number one). On the negative side, it can be difficult to debug when things go wrong and is another build system you have to learn. I find this frustrating, as I’m already familiar with so many other build tools--setuptools, pip, Make, Ant, buildout, Paver, and more. I’d have preferred to see Buildozer’s features incorporated into the standard Python ecosystem. However, since they weren’t and it’s here and it works, I’m going to show you how to use it!

Getting Buildozer

Buildozer lives on Kivy’s GitHub page. It’s currently alpha software, but it’s far less cumbersome than older Kivy deployment methods, so I recommend you use it. I also recommend you use the master branch rather than one of the releases. The Kivy developers keep this project up-to-date with the ever-changing landscape of Android and iOS development, and it’s rarely in a broken state. Unfortunately, however, Buildozer is not currently supported on Windows. In fact, there is no reliable way to deploy Kivy to Android devices from a Windows environment. The Kivy team provides a Linux virtual machine that you can run from inside your Windows box if you want to tackle deploying, but it’s not a painless process.

If you have Git installed, which you should if you’re serious about software development, the easiest way to install Buildozer is to clone the repository using the simple command git clone https://github.com/kivy/buildozer.git. I’d do this from whatever directory holds the code for your Kivy project so you can keep all your Kivy stuff in one place.

If you’re just toying around and prefer not to install Git at this time, you can download a ZIP file directly from the Buildozer GitHub page. Click the Download ZIP button, which at the time of this writing appears in the right sidebar on the GitHub page, as shown in Figure 9-1.

github download button

Figure 9-1. The GitHub Download button

Once you’ve downloaded and extracted the Buildozer file, cd into the new buildozer directory and run the command python2 setup.py install. You may have to run this with sudo, depending on how your Python and operating system are set up.

Deploying Your Application

In this section, I’ll focus on deploying your weather app to an Android device, as it’s a bit simpler than for iOS. The iOS section will build on it, so you’ll want to read both sections if you’re an Apple user.

TIP

At the time of writing, even though Kivy supports Python 3, Buildozer is Python 2 only and installs a Python 2 environment onto your Android device. This means that if you have been developing in Python 3, you may have to make a couple of changes, which I have noted throughout the book, to get the application running on Android.

Namely, there is a call to super that needs to be changed to Python 2 format, and the clear method on the list needs to be replaced with an explicit del.

Buildozer works from a file named buildozer.spec, which allows you to describe and configure how your Kivy application will be deployed. Start by generating a default specification running the command buildozer init in the folder that contains your main.py and weather.kv files. Open the resulting buildozer.spec file in your text editor. You’ll find that it contains a ton of settings, some of which are commented out, and all of which are described inside the file. Example 9-1 illustrates the changes I made to the default settings.

Example 9-1. Buildozer specification for an Android build

[app]

# (str) Title of your application 1

title = Weather

# (str) Package name 2

package.name = weather

# (str) Package domain (needed for android/ios packaging)

package.domain = org.example

# (str) Source code where the main.py live

source.dir = .

# (list) Source files to include (let empty to include all the files)

source.include_exts = py,png,jpg,kv,atlas

# (list) Source files to exclude (let empty to not exclude anything)

#source.exclude_exts = spec

# (list) List of directory to exclude (let empty to not exclude anything)

#source.exclude_dirs = tests, bin

# (list) List of exclusions using pattern matching

#source.exclude_patterns = license,images/*/*.jpg

# (str) Application versioning (method 1)

# version.regex = __version__ = '(.*)'

# version.filename = %(source.dir)s/main.py

# (str) Application versioning (method 2) 3

version = 0.1

# (list) Application requirements 4

requirements = kivy

# (str) Presplash of the application

#presplash.filename = %(source.dir)s/data/presplash.png

# (str) Icon of the application

#icon.filename = %(source.dir)s/data/icon.png

# (str) Supported orientation (one of landscape, portrait or all) 5

orientation = all

# (bool) Indicate if the application should be fullscreen or not

fullscreen = 0

#

# Android specific 6

#

# (list) Permissions

android.permissions = INTERNET

# (int) Android API to use

android.api = 14

# (int) Minimum API required (8 = Android 2.2 devices)

android.minapi = 8

# (int) Android SDK version to use

android.sdk = 21

# (str) Android NDK version to use

android.ndk = 9

# (bool) Use --private data storage (True) or --dir public storage (False)

android.private_storage = True

# (str) Android NDK directory (if empty, it will be automatically downloaded.)

#android.ndk_path =

# (str) Android SDK directory (if empty, it will be automatically downloaded.)

#android.sdk_path =

# (str) Android entry point, default is ok for Kivy-based app

#android.entrypoint = org.renpy.android.PythonActivity

# (list) List of Java .jar files to add to the libs so that pyjnius can access

# their classes. Don't add jars that you do not need, since extra jars can slow

# down the build process. Allows wildcards matching, for example:

# OUYA-ODK/libs/*.jar

#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar

# (list) List of Java files to add to the android project (can be java or a

# directory containing the files)

#android.add_src =

# (str) python-for-android branch to use, if not master, useful to try

# not yet merged features.

#android.branch = master

# (str) OUYA Console category. Should be one of GAME or APP

# If you leave this blank, OUYA support will not be enabled

#android.ouya.category = APP

# (str) Filename of OUYA Console icon. It must be a 732x412 png image.

#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png

# (str) XML file to include as an intent filters in <activity> tag

#android.manifest.intent_filters =

# (list) Android additionnal libraries to copy into libs/armeabi

#android.add_libs_armeabi = libs/android/*.so

# (bool) Indicate whether the screen should stay on

# Don't forget to add the WAKE_LOCK permission if you set this to True

#android.wakelock = False

# (list) Android application meta-data to set (key=value format)

#android.meta_data =

# (list) Android library project to add (will be added in the

# project.properties automatically.)

#android.library_references =

#

# iOS specific

#

# (str) Name of the certificate to use for signing the debug version

# Get a list of available identities: buildozer ios list_identities

#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"

# (str) Name of the certificate to use for signing the release version

#ios.codesign.release = %(ios.codesign.debug)s

[buildozer]

# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) 7

log_level = 2

# -----------------------------------------------------------------------------

# List as sections

#

# You can define all the "list" as [section:key].

# Each line will be considered as a option to the list.

# Let's take [app] / source.exclude_patterns.

# Instead of doing:

#

# [app]

# source.exclude_patterns = license,data/audio/*.wav,data/images/original/*

#

# This can be translated into:

#

# [app:source.exclude_patterns]

# license

# data/audio/*.wav

# data/images/original/*

#

# -----------------------------------------------------------------------------

# Profiles

#

# You can extend section / key with a profile

# For example, you want to deploy a demo version of your application without

# HD content. You could first change the title to add "(demo)" in the name

# and extend the excluded directories to remove the HD content.

#

# [app@demo]

# title = My Application (demo)

#

# [app:source.exclude_patterns@demo]

# images/hd/*

#

# Then, invoke the command line with the "demo" profile:

#

# buildozer --profile demo android debug

1

Change the title to something meaningful.

2

The package name should be unique to your dev environment. It will be concatenated to the package.domain, to create something that is globally unique.

3

There are two ways to specify the version: scraping it from main.py or specifying it in buildozer.spec. The former is a better idea if your version number is referenced in multiple places, but since I’m only using the version for deploying, I specify it here. You have to remember to bump your version with each new release, or Google Play and the iTunes App Store won’t pick it up.

4

This is a list of third-party (nonstandard library) Python modules your app depends on. The weather app depends only on Kivy, but you can specify any other libraries you may require.

5

The weather app can take advantage of autorotating.

6

Uncomment several Android-specific options.

7

Things will probably crash, so a debugging log_level is a good idea. Reset this before making your app public, or use the Buildozer Profiles feature.

Having updated this file to suit your fancy (you may want to add an icon, for example), you are almost ready to push it to your phone. The first thing you need to do is enable developer access on your phone, if it’s running a recent version of Android. You might want to search the Web for how to do this on your specific device, but for recent versions of Android, it typically means finding the Build Number in the About menu (for me, it’s buried inside About → Software information → More → Build Number) and tapping on it seven times. This is a strange, magical incantation, but eventually it’ll tell you that you’ve unlocked developer options on your phone. You’ll then need to find the “Developer options” menu and enable USB Debugging.

The next step is to run the command buildozer android debug deploy run. You can easily tell what this command is supposed to do, but it will probably fail multiple times before you get it running. If it fails, edit the buildozer.spec file. Find the section titled [buildozer] toward the end of the file and change the log_level setting to 2 for debugging. Next time you run Buildozer, it will give you informative errors telling you that you are missing dependencies. I personally had to install a ton of packages, including Cython, virtualenv, g++, Ant, and a Java compiler before it would run. However, on the fourth attempt, it managed to complete after taking ages to download all the Android dependencies (don’t do this on a rate-limited 3G network!), compile them, compile the .apk, upload it to the phone, and, eventually, open it on the phone. Go grab a coffee. Assuming the app is working, when you get back you should be able to interact with it just as you did when it was running on your desktop.

Of course, it’s not safe to assume everything is working. Android has a variety of useful debugging tools that you can use to debug your application. I can’t describe them all here, but I’ll discuss a couple that I think are absolutely vital.

Buildozer provides the logcat command, which calls adb logcat under the hood (you may already be familiar with Android Debug Bridge, or adb). You can run it with buildozer android logcat. This command provides all sorts of diagnostic information in the terminal. Most importantly, you can see any Python or Java tracebacks related to your program. Try running buildozer run in a different terminal while the log is being displayed. You’ll see the Kivy logging info pop up, and if you’ve got a Python error in your program, you’ll see the traceback.

Another tool that is useful (more for prototyping and experimenting than for debugging) is the Kivy Remote Shell. You can access it from Kivy’s GitHub page using Git or by downloading the ZIP file as you did with Buildozer. Kivy Remote Shell ships with a Buildozer spec file. Once you have cloned it, you simply have to run buildozer android debug deploy run to get the Kivy Remote Shell installed.

But what is the Kivy Remote Shell? It’s kind of the Python shell equivalent of adb logcat. You can run the app on your phone and then connect to it from a computer on the same wireless network using ssh (ssh is preinstalled on most Linux and Mac OS systems; on Windows, you’ll want to look up a program called PuTTy). The exact command you need to enter to log in to the device is displayed on the phone’s screen. You’ll be asked for a password, which is kivy. Once you’ve logged in, you’ll be presented with a Python prompt that you can use to control code on the phone. The app object is available, and you can add Kivy widgets to the window using app.root.add_widget. This is useful for seeing how layouts look on the phone, but I mostly use it for experimenting with pyjnius, the Python-to-Java bridge that can be used for manipulating Java Android classes on a phone.

Finally, the absolute best tool for Kivy debugging is called Internet Relay Chat (IRC). If you’re having trouble getting Buildozer working, get in touch with the Kivy team using IRC or the Kivy mailing list. Some of the tools discussed in this chapter are still alpha, and bug reports from people who are willing to help the developers understand the problems are the most important means of turning them into the solid software that is the rest of Kivy.

IN WHICH THE AUTHOR DIGRESSES

I have a strong aversion to having separate interfaces for developers and so-called “normal” users. I believe this creates a culture of inequality. The unbelievable arrogance of the software engineer who decided that normal users needed to be protected from developer options is disturbing and disheartening. This engineer was clearly a “deatheater” who thinks the “muggles” of the world need to be kept further in the dark, rather than enlightened.

The world is coming to entirely depend on technology, and we need to welcome the inexperienced users, make them feel comfortable, help them become adept, and, if we are lucky, prompt them to become new developers themselves. The generation of children who grew up on locked-down tablets and mobile phones never had a chance to explore and experiment, to break and fix things. If you have kids, buy them a Raspberry Pi to prevent another generation from missing the chance for free learning. Assuming that your users cannot handle the systems you give them is the most horrible disservice you can do them.

This does not mean that interfaces should not be intuitive and easy to use. A system that is easy to use should be equally accessible for experienced power users and new users. Setting sensible defaults, using convention over configuration, and implementing other paradigms can ease this process. But the developer culture must stop enforcing user ignorance.

Deploying to iOS

Unlike Android, Apple’s draconian development policies require that you have an Apple developer license before you can test or debug your application on an iOS device. This allows Apple to ensure the quality of the applications available for these devices. For normal iOS development, Apple provides access to an emulator tool, but for Kivy, you’ll probably want to test on a real device. If you don’t already have a developer license, follow the instructions on the Apple developer website to get one.

Once you’ve paid your dues and logged in to the Apple development center, you can download and install XCode if you don’t have it already. Run XCode and plug in your iOS device. The XCode Device Organizer should pop up automatically. Select the iOS device from the menu and click the “Use for Development” button. You will be prompted for an Apple ID that is associated with a development account. You will then be invited to request a certificate for the device. Follow this process and confirm that the certificate shows up in your Apple Developer Account in the web browser.

That should be all the extra work you need to do to get permission to develop from Apple. You can now return to the Kivy development process. Create a buildozer.spec file similar to Example 9-1 (if you are deploying to both Android and iOS, you can use the same buildozer.spec file). You can ignore the Android section this time, but you’ll need to add a string to the “iOS specific” section. This string is constructed from the certificate you just created, and can be tricky to get right. Luckily, Buildozer has a command to do this for you: buildozer ios list_identities. If your XCode is set up and your iOS device is connected, you should get output like Example 9-2.

Example 9-2. Output of buildozer ios list_identities

1) <LONG HEX STRING> "iPhone Developer: Your Full Name (<HEXSTRING>)"

1 valid identities found

Available identities:

- "iPhone Developer: Your Full Name (<HEXSTRING>)"

Copy the contents of the quoted string under “Available identities” into the “iOS specific” section of your buildozer.spec file, remembering to uncomment both ios.codesign lines. See Example 9-3 (other than this section, the file is unmodified from Example 9-1).

Example 9-3. Buildozer specification for an iOS build

#

# iOS specific

#

# (str) Name of the certificate to use for signing the debug version

# Get a list of available identities: buildozer ios list_identities

ios.codesign.debug = "iPhone Developer: Dustin Phillips (XAQ2M755YE)"

# (str) Name of the certificate to use for signing the release version

ios.codesign.release = %(ios.codesign.debug)s

Now you can run the command buildozer ios debug deploy run, similarly to the Android deployment. If all goes well, your Kivy app should run cleanly on your iOS device.

BUILDOZER DEPENDENCIES ON MAC OS

The first time I ran Buildozer on my Mac, it complained about some missing packages, including pkgconfig, autoconf, and automake. You may have to do some research to figure out how to install these if you don’t already have them. You will have the best luck using homebrew or macports, very useful tools if you do much Unix programming.

Alternatively, you can manually download and install packages with typical Unix commands (available if you have XCode installed). For example, I had to download the latest version of pkg-config and install it using the typical Unix process:

./configure --with-internal-glib

make

sudo make install

Unfortunately, I can’t guess which dependencies you might need to install or already have available. Dependency management is an integral part of programming, and you’ll have to get good at it for whichever operating system you choose to use. Normally, the Buildozer process will fail with a clean error message telling you what you need to install, but it won’t tell you how. Stack overflow and a web search are definitely your friends here.

For debugging, you can open XCode and view the logging output from the device, similar to what you see when you run the logcat command on Android. The command buildozer xcode will open the actual XCode project associated with your Kivy project.

Android Bonus: Accessing the GPS

Before I get into this section, I want to apologize to iOS users for not including them. The Kivy team has developed an API for accessing native features (including the accelerometer, camera, GPS, and notifications) on a variety of operating systems. Sadly, like Buildozer, the library is still in alpha as I write this, and the only feature supported on iOS is the accelerometer. The good news is that you can use this API with your Android device, and once the Kivy team adds support for iOS devices, your code should run virtually unmodified. The even better news is that all Kivy libraries are completely open source, and the team would be delighted to receive a pull request if you add iOS support for their API.

If you aren’t currently deploying to an Android device, you can skip to the next section.

The Kivy team has created an awesome library called pyjnius that allows you to access Java classes from inside Python. This is how they provide all the Kivy goodness on Android. There is a similar library for accessing Objective-C classes on iOS called pyobjus. Both of these are very powerful, but they require a deep knowledge of the underlying classes being accessed. Fortunately, the Kivy team has also created a platform-independent API called plyer that wraps these features in an easy-to-use and consistent Python API.

The plyer library is not bundled with Kivy, so you’ll need to install it yourself. You can do this through Git or by downloading the ZIP file as you did with Buildozer. The command python setup.py install should install it for you.

The next step is to hook up the Current Location button on the Add Location form to a method that knows how to use the GPS. Example 9-4 illustrates.

Example 9-4. Callback for the Current Location button

Button:

text: "Current Location"

size_hint_x: 25

on_press: root.current_location()

Of course, this will just crash the app if you don’t actually add the method being called. You’ll need a few new imports first, as shown in Example 9-5.

Example 9-5. Imports for accessing the GPS

fromplyerimport gps

fromkivy.clockimport Clock, mainthread

fromkivy.uix.popupimport Popup

fromkivy.uix.labelimport Label

The method itself, along with a stub callback, is displayed in Example 9-6.

Example 9-6. Start accessing the GPS and show a pop-up if it’s not available

def current_location(self):

try:

gps.configure(on_location=self.on_location)

gps.start()

exceptNotImplementedError:

popup = Popup(title="GPS Error",

content=Label(text="GPS support is not implemented on your platform")

).open()

Clock.schedule_once(lambda d: popup.dismiss(), 3)

@mainthread

def on_location(self, **kwargs):

print(kwargs)

The @mainthread decorator essentially wraps the method in a Clock.schedule_once call so that it always runs on the main Kivy thread. Depending on the underlying implementation, the plyer GPS module may be executed in a background thread that isn’t allowed to access the Kivy main loop.

I tested this first on my Linux laptop, for which GPS is not supported. As expected, the pop-up is displayed for three seconds and then disappears. The next step is to check it out on the Android phone. However, before you can do that, you need to add the plyer requirement and a couple of permissions to your buildozer.spec file, as shown in Example 9-7 and Example 9-8.

Example 9-7. Accessing GPS permissions

# (list) Permissions

android.permissions = INTERNET,ACCESS_FINE_LOCATION,ACCESS_COARSE_LOCATION

Example 9-8. Depending on plyer

# (list) Application requirements

requirements = kivy,plyer

If you now run buildozer android debug deploy run logcat, your app should pop up on the phone, and when you click the Current Location button on the Add Location form, you should get some output on the console. Now all you need to do is implement the on_locationfunction to look up the user’s current location on Open Weather Map. Luckily, Open Weather Map has an API to request by latitude and longitude. We could request the weather details that way, but as you can see in Example 9-9, I cheated and just grabbed the location from the response. Then I pass this into the show_current_weather method, and presto, everything works!

Example 9-9. Looking up the location on Open Weather Map

@mainthread

def on_location(self, **kwargs):

search_template = "http://api.openweathermap.org/data/2.5/" +

"weather?lat={}&lon={}"

search_url = search_template.format(kwargs['lat'], kwargs['lon'])

data = requests.get(search_url).json()

location = (data['sys']['country'], data['name'])

WeatherApp.get_running_app().root.show_current_weather(location)

Run buildozer android debug deploy run again. If you hit up the Add Location form and touch Current Weather, it should locate you (if your GPS is on) and search for the weather wherever you are.

Keeping It Running

You’ll notice that as soon as the screen turns off, your Kivy app exits, and it restarts when you turn it on again. This isn’t a horrible thing for a weather application, but it’s not exactly desirable. Luckily, Kivy supports a so-called pause mode that allows you to keep the window open. All you have to do is add an on_pause method to the WeatherApp class that returns True instead of the default False. See Example 9-10.

Example 9-10. Enabling pause on mobile

def on_pause(self):

return True

Deploy it to your phone using Buildozer, and you can switch back and forth between your Kivy app and other applications with ease.

File It All Away

Your Kivy app is functionally complete! It runs on your Android or iOS phone, and you have app files (they live in the bin directory) that can be submitted to the Google Play Market or iTunes App Store. In my experience, a program is never finished. However, books have endings, so there are several things I will leave you to explore on your own:

§ The plyer library provides access to the accelerometer. You could create a “shake to change from forecast to current weather” feature.

§ plyer also has a notification system that you can integrate in a few different ways.

§ If you have Android or iOS experience (or if you want to get some), see if you can figure out how to access other features of the underlying OS using pyjnius or pyobjus.

§ Write another Kivy app from scratch. And another and another!

That’s all, folks. I hope you’ve enjoyed reading this book at least half as much as I’ve enjoyed writing it. I also hope you’ve learned even more than I did from the process!