Kivy runs on android using the
python-for-android project, which
support
android services. This works in a simple way, you basically bundle two
main.py
in your application, one for the UI, one for the service. The UI can
start the services on start. From that point though, things may be a little
uneasy. Why? Because you have two applications, and now you have to make them
talk to each over if you want to do anything useful.
Android’s way of having an Activity and a Service talk to each other is
Broadcast signals, which can be limited to part of your applications, howether,
it’s not straightforward to use them with
pyjnius,
which is the magical interface we use to use java code from python.
Another way is to use network, i’ve been doing it with twisted in the past,
setting a twisted server in the Service, and using twisted as a client in the
Activity, howether, i find this to be heavy lifting for the trivial task of
communicating between two programs on the same device. And including Twisted in
your app, certainly add some weight to it.
Howether, in order to support TUIO, kivy ships with a simple OSC
implementation, OSC is a simple connectionless network protocol, that allow you
to pack messages, and send them to an ip/port/api URI, turns out we don’t need
anything more, and a connectionless protocol avoid us dealing with
disconnections (like the UI being closed, or the service not being started yet)
that could give us some headaches (and certainly gave me some). We just need to
have both part of the program listen for OSC messages, and have them send data
to each other, if confirmation is needed, it’s possible to have a messages been
sent back on a specific api.
So let’s get started.
getting a minimal kivy app
The usual things, let’s put a simple UI with a Button.
from kivy.app import App
from kivy.lang import Builder
kv = '''
Button:
text: 'push me!'
'''
class ServiceApp(App):
def build(self):
return Builder.load_string(kv)
if __name__ == '__main__':
ServiceApp().run()
here we just load a kv string that defines a button, and return the result,
nothing fancy.
getting a minimal service
from time import sleep
if __name__ == '__main__':
while True:
sleep(.1)
Yeah, nothing much needed, actually, the sleep isn’t even needed, but the
program have to run, and we’ll need this loop anyway.
starting the service
For your service to run, you need to tell your UI to start it.
from kivy.app import App
from kivy.lang import Builder
from kivy.utils import platform
kv = '''
Button:
text: 'push me!'
'''
class ServiceApp(App):
def build(self):
if platform == 'android':
from android import AndroidService
service = AndroidService('my pong service', 'running')
service.start('service started')
self.service = service
return Builder.load_string(kv)
if __name__ == '__main__':
ServiceApp().run()
We make the test for android, so you can still test your app on desktop, by
starting manually both parts.
Packaging them for android
both files must be named main.py
, the UI one is at the root of the project,
the other one is in a service
directory directly under the root of the
project.
├── main.py
└── service
└── main.py
To package, i’ll be using buildozer.
As we are using network don’t forget to add the NETWORK permission when editing
buildozer.spec
buildozer init
editor buildozer.spec
buildozer android debug deploy run logcat
After some time, you should see the (not very exciting) app start on your
plugged android device.
setting up OSC
Whatever the side, OSC needs a port to listen on, and to have functions to call
when things happen. The basic setup is simple.
from kivy.lib import osc
def some_api_callback(message, *args):
print("got a message! %s" % message)
osc.init()
oscid = osc.listen(ipAddr='0.0.0.0', port=someport)
osc.bind(oscid, some_api_callback, '/some_api')
and then
osc.readQueue(oscid)
needs to be called regularly.
for the service, we’ll just put this call in the loop:
from time import sleep
from kivy.lib import osc
service = 3000
def some_api_callback(message, *args):
print("got a message! %s" % message)
if __name__ == '__main__':
osc.init()
oscid = osc.listen(ipAddr='127.0.0.1', port=service)
osc.bind(oscid, some_api_callback, '/some_api')
while True:
osc.readQueue(oscid)
sleep(.1)
And for UI, we’ll use kivy.clock.Clock’s schedule_interval
method.
from kivy.app import App
from kivy.lang import Builder
from kivy.lib import osc
from kivy.utils import platform
from kivy.clock import Clock
activityport = 3001
def some_api_callback(message, *args):
print("got a message! %s" % message)
kv = '''
Button:
text: 'push me!'
'''
class ServiceApp(App):
def build(self):
if platform == 'android':
from android import AndroidService
service = AndroidService('my pong service', 'running')
service.start('service started')
self.service = service
osc.init()
oscid = osc.listen(ipAddr='127.0.0.1', port=activityport)
osc.bind(oscid, some_api_callback, '/some_api')
Clock.schedule_interval(lambda *x: osc.readQueue(oscid), 0)
return Builder.load_string(kv)
if __name__ == '__main__':
ServiceApp().run()
Now, both sides can receive messages, that’s a good first step, but nothing
will really happen, since none of them send any message.
sending messagse
The osc api to send message is very simple:
osc.sendMsg(api, data_list, port=someport, ipAddr=someaddress)
now, ipAddr is by default localhost, which is fine for us, so we only need to
find the api we want to hit, the data list, and the port, which will be the one
the other side listens on.
Let’s make our button send a message to the Service.
from kivy.app import App
from kivy.lang import Builder
from kivy.lib import osc
from kivy.clock import Clock
activityport = 3001
serviceport = 3000
def some_api_callback(message, *args):
print("got a message! %s" % message)
kv = '''
Button:
text: 'push me!'
on_press: app.ping()
'''
class ServiceApp(App):
def build(self):
if platform == 'android':
from android import AndroidService
service = AndroidService('my pong service', 'running')
service.start('service started')
self.service = service
osc.init()
oscid = osc.listen(ipAddr='127.0.0.1', port=activityport)
osc.bind(oscid, some_api_callback, '/some_api')
Clock.schedule_interval(lambda *x: osc.readQueue(oscid), 0)
return Builder.load_string(kv)
def ping(self):
osc.sendMsg('/some_api', ['ping', ], port=someotherport)
if __name__ == '__main__':
ServiceApp().run()
Yes, at that point, you can run it, and see that when you press the button,
your adb logcat on android, yay!
Now, let’s make our service answer, and our UI display the answer!
from time import sleep
from kivy.lib import osc
serviceport = 3000
activityport = 3001
def some_api_callback(message, *args):
print("got a message! %s" % message)
answer_message()
def answer_message():
osc.sendMsg('/some_api', [asctime(localtime()), ], port=activityport)
if __name__ == '__main__':
osc.init()
oscid = osc.listen(ipAddr='127.0.0.1', port=serviceport)
osc.bind(oscid, some_api_callback, '/some_api')
while True:
osc.readQueue(oscid)
sleep(.1)
and for the UI to answer:
from kivy.app import App
from kivy.lang import Builder
from kivy.lib import osc
from kivy.utils import platform
from kivy.clock import Clock
activityport = 3001
serviceport = 3000
kv = '''
Button:
text: 'push me!'
on_press: app.ping()
'''
class ServiceApp(App):
def build(self):
if platform == 'android':
from android import AndroidService
service = AndroidService('my pong service', 'running')
service.start('service started')
self.service = service
osc.init()
oscid = osc.listen(ipAddr='127.0.0.1', port=activityport)
osc.bind(oscid, some_api_callback, '/some_api')
Clock.schedule_interval(lambda *x: osc.readQueue(oscid), 0)
return Builder.load_string(kv)
def ping(self):
osc.sendMsg('/some_api', ['ping', ], port=someotherport)
def some_api_callback(self, message, *args):
print("got a message! %s" % message)
self.root.text += '\n%s' % message[2]
if __name__ == '__main__':
ServiceApp().run()
The only thing a bit confusing here is that the real message is in message[2],
don’t ask me why, it’s probably explained in some documenation i didn’t care
enough to search for :).
conclusion
That’s not much code! And it should be quite easy to extend to allow for more
complex patterns, detecting if your service is running or not can be done by
making it send pings
at regular intervals, you can also make your service
fetch/work on data from somewhere else in the background, and pass it to the UI
when it’s ready.
A slightly more complex demo based on this can be found
here.