When alone Kivy is not enough
Everybody likes games, and almost everybody likes Python Development 🙂 One interesting tool to join these two things together is Kivy.
Few words about Kivy – Python framework
It’s a cross-platform Python library that can be very useful in the rapid development of applications (not only games) that make use of innovative user interfaces, such as e.g. multi-touch apps.
Kivy’s execution speed is comparable to the native mobile alternative, Java for Android or Objective C for iOS. Moreover, Kivy has the huge advantage of being able to run on multiple platforms, just as HTML5 does; in which case, Kivy performs better because it doesn’t rely on a heavy browser, and many of its components are implemented in C using the Cython library in such a way that most of the graphics processing runs directly in the GPU [2].
A typical application written in Kivy consists of two files: one in Python (*.py, application logic) and the second in Kivy language (*.kv, design language specifically geared towards easy and scalable GUI design). Below example is a ‘Hello World’ in Kivy. First file:
# File name: hello.py
from kivy.app import App
from kivy.uix.button import Label
class HelloApp(App):
def build(self):
return Label()
if __name__=="__main__":
HelloApp().run()
and the second:
# File name: hello.kv
#:kivy 1.9.0
Awesome Pong Game tutorial learns basics of Kivy in an attractive way.
Portability in Kivy
Kivy applications are generally portable across all supported platforms – Mac, Windows, Linux, iOS, Android, and Raspberry Pi with no significant problems. Kivy strives to be as cross-platform as possible and delivers a similar user experience on every system it supports. This is a huge feature by itself; as a plus, one has the ability to write code once and run everywhere with little (or not at all) tweaks [1][2].
The downside of being cross-platform, however, is that one can only rely on the core functionalities supported by every system. This feature set includes i.a. rendering graphics on the screen, reproducing a sound if there is a sound card, accepting user input, and not too much else.
Each Kivy app, by the virtue of being written in Python, also has access to the vast Python standard library. It facilitates networking, supports a number of application protocols, and provides many general-purpose algorithms and utility functions.
Still, the input-output (I/O) capabilities of a “pure Kivy” programs are limited to those that are present on most platforms. This amounts to a tiny fraction of what a common computer system, such as a smartphone or a tablet PC, can actually do [1][2].
Features that are directly available in Python or Kivy:
• Hardware-accelerated graphics,
• Touch screen input with optional multi-touch,
• Sound playback,
• Networking [2].
Features that aren’t supported or require an external library:
• Modem, support for voice calls, and SMS,
• Use of built-in cameras (videos and taking pictures),
• Use of a built-in microphone to record sound,
• Cloud storage for application data, associated with a user account,
• Bluetooth,
• Location services and GPS,
• Fingerprinting and other biometric security,
• Motion sensors (accelerometer and gyroscope),
• Screen brightness control,
• Vibration and other forms of feedback,
• Battery charge level [2][3].
For many entries from the above list different Python libraries exist and they can handle many platform specific tasks, for example Pyjnius (accessing Java classes from Python), Audiostream (provides an easy-to-use API for streaming bytes to the speaker), Plyer (accessing features of your hardware) and others.
For example, using Plyer one can get information about battery (status and percentage of charging) and display it in a clear way:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.lang import Builder
from kivy.properties import StringProperty, ObjectProperty
from plyer import battery
Builder.load_string('''
:
lbl1: lbl1
lbl2: lbl2
FloatLayout:
Button:
size_hint_y: None
pos_hint: {'y': .5}
text: "Battery Status"
on_press: root.get_status()
BoxLayout:
size_hint_y: None
pos_hint: {'y': .1}
Label:
text: "Is Charging?"
Label:
id: lbl1
text:
Label:
text: "Percentage"
Label:
id: lbl2
text:
''')
class BatteryInterface(BoxLayout):
lbl1 = ObjectProperty()
lbl2 = ObjectProperty()
def get_status(self, *args):
self.lbl1.text = str(battery.status['isCharging'])
self.lbl2.text = str(battery.status['percentage']) + "%"
class BatteryApp(App):
def build(self):
return BatteryInterface()
if __name__ == "__main__":
app = BatteryApp()
app.run()
It’s not like these aforementioned features are completely unavailable to application. The challenge is that these parts of functionality are fragmented across different platforms (or even consecutive versions of the same platform, for example, Android); thus, one ends up writing platform-specific, not portable code anyway [2].
Kivy + Android
A lot of functionalities are available on Android and it is only partially covered by an existing Python or Kivy API. It creates possibility to use platform-specific features in one’s application. Unlike pure Kivy, the underlying Android API certainly provides us with ways of recording sound programmatically [1][2].
And here comes the Pyjnius with help. This library allows for using Java classes directly from Python. It means that one can have full access to the native Android API and the official Android documentation, which is obviously more suited for Java development, not Python. Pyjnius allows also for using the official Python interpreter (CPython), together with a multitude of libraries such as NumPy, which facilitates very fast computations [2].
Using Java’s native API
Let’s assume that our task is to create an application that allows recording a sound. The most suitable Java classes for purposes of sound recording are MediaRecorder and MediaPlayer. Also adding Environment class would be convenient, cause it provides access to many useful environmental variables.
To load the aforementioned classes into Python application one can use code as follows:
from jnius import autoclass
Environment = autoclass('android.os.Environment')
MediaRecorder = autoclass('android.media.MediaRecorder')
AudioSource = autoclass('android.media.MediaRecorder$AudioSource')
OutputFormat = autoclass('android.media.MediaRecorder$OutputFormat')
AudioEncoder = autoclass('android.media.MediaRecorder$AudioEncoder')
Looks quite nice. The simple practical example below shows how to do one thing (figure out where SD card is mounted) in two ways:
//code in Java
import android.os.Environment;
String path = Environment.getExternalStorageDirectory().getAbsolutePath();
and
# code in Python
Environment = autoclass('android.os.Environment')
path = Environment.getExternalStorageDirectory().getAbsolutePath()
Original Java code aimed at recording sound with Android can be found here. To initialize MediaRecorder object one can use the code below:
storage_path = (Environment.getExternalStorageDirectory()
.getAbsolutePath() + '/kivy_recording.3gp')
recorder = MediaRecorder()
def init_recorder():
# set the audio source for microphone
recorder.setAudioSource(AudioSource.MIC)
# 3GPP media file as a output format
recorder.setOutputFormat(OutputFormat.THREE_GPP)
# Adaptive Multi-Rate with Narrow Band codec
recorder.setAudioEncoder(AudioEncoder.AMR_NB)
recorder.setOutputFile(storage_path)
recorder.prepare()
And then our recorder object and init_recorder function could be used in application class, for example in this way [2]:
class RecorderApp(App):
is_recording = False
def begin_end_recording(self):
if (self.is_recording):
recorder.stop()
recorder.reset()
...
init_recorder()
recorder.start()
...
One of few available full examples of code (in Kivy) of the recording app could be found here.
Deploying to Android
Kivy has a custom-built deployment tool called Buildozer. Buildozer is a tool that aims to package mobiles application easily. It automates the entire build process, downloads the prerequisites like python-for-android, Android SDK, NDK, etc. Buildozer manage a file named buildozer.spec in your application directory, describing your application requirements and settings such as title, icon, included modules etc. It will use the specification file to create a package for Android, iOS, and more [1]. Buildozer provides the logcat command, which calls adb logcat under the hood (adb stands for Android Debug Bridge). One 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. One can also use python-for-android directly, which is described here [1][2].
The ability to immediately launch and test an application without compiling is an incredibly important aspect of Kivy development. The Kivy Launcher app, which one may find very useful to test his own programs, is available in the form of an .apk file from the official Kivy’s website this will be useful for emulated Android devices that don’t have access to Google Play.
The only prerequisite for testing operation is an Android device, physical or virtualized, with the Kivy Launcher application installed and working [2].
Conclusion
When one writes non-portable code it has strengths and weaknesses. This particular choice is difficult, because the switch to native API at the beginning of the project may be completely impractical to undo at a later stage.
With platform-specific code, one can do virtually anything that the platform is capable of. There are no artificial limits; our Python code has unrestricted access to the same underlying API as the native code. On the downside, depending on a single platform is risky because porting the program to a new system becomes harder with every platform-specific feature one can use.
References
- Philips D., “Creating Apps inn Kivy”, O’Reilly, 2014.
- Vasilkov M., “Kivy Blueprints”, Packt Publishing, 2015.
- Ulloa R., “Kivy – Interactive Applications and Games in Python, Second Edition”, Packt Publishing, 2015.