Plyer: Platform independent compatibility layer
Plyer is an open source library to access features commonly found in various platforms via python. It is under heavy development with current version being 1.3.0
and provides support for Windows, MacOX, Linux, Android and iOS.
Content
- History of plyer
- How to use it?
- An example with kivy
- How plyer works?
- Internal working
- Contributing.
- Conclusion.
History of plyer.
Kivy organisation started plyer project as a need to get access of various platform features since kivy organisation provides the framework to build cross-platform apps and softwares using python.
For example, In order to trigger the phone calling interface in the mobile devices, one needs to access the libraries provided by the respective platform in the native language itself.
For android one needs to get android.content.Intent
and android.net.Uri
and start a new activity with intent action Intent.ACTION_CALL
and Intent.setData
as telephone number to trigger a call.
For iOS one needs to get UIApplication
and NSURL
with tel
(telephone number) as an object_str
to trigger a call.
But how to do the same using python? To tackle this problem kivy started 3 projects:
PyJnius, PyObjus and Plyer.
Pyjnius: A Python module to access Java classes as Python classes using JNI.
Pyobjus: Python module for accessing Objective-C classes as Python classes using Objective-C runtime reflection.
Plyer: A platform-independent api to use features commonly found on various platforms in Python like Accelerometer, Calling, Text to speech, Wifi, Barometer and many more.
How to use plyer?
Using plyer is super easy.
Get accelerometer readings::
>>> from plyer import accelerometer
>>> accelerometer.enable()
>>> accelerometer.acceleration
(-10.048464775085449, 6.825869083404541, 7.7260890007019043)
Get battery status::
>>> from plyer import battery
>>> battery.status
{'percentage': 82.0, 'isCharging': False}
Turn on flash::
>>> from plyer import flash
>>> flash.on()
I highly recommend you to try Kivy Remote Shell to see these examples work on a real android device. One important point to stress upon is that Plyer is independent of Kivy and can work as a separate entity.
Example with kivy.
Text to Speech converter. Just copy paste the following code and make sure you have kivy installed with version greater than 1.8.0.
Create a main.py
file.
import kivy
kivy.require('1.8.0')
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from plyer import tts
class Text2SpeechDemo(BoxLayout):
def do_read(self):
try:
tts.speak(self.ids.notification_text.text)
except NotImplementedError:
popup = ErrorPopup()
popup.open()
class Text2SpeechDemoApp(App):
def build(self):
return Text2SpeechDemo()
def on_pause(self):
return True
class ErrorPopup(Popup):
pass
if __name__ == '__main__':
Text2SpeechDemoApp().run()
Create a text2speechdemo.kv
file. [name for .kv file should be same]
<Text2SpeechDemo>:
BoxLayout:
orientation: 'vertical'
padding: 20
TextInput:
id: notification_text
text: 'Put message here'
Button:
text: 'Read'
size_hint_y: 0.2
on_press: root.do_read()
<ErrorPopup>:
size_hint: .7, .4
title: "Error"
BoxLayout:
orientation: 'vertical'
padding: 10
spacing: 20
Label:
size_hint_y: 0.4
text: "This feature has not yet been implemented in Plyer."
Button:
text: 'Dismiss'
size_hint_y: 0.4
on_press: root.dismiss()
Run the application using python main.py
in any of the supported platforms listed in the introduction and use this awesome feature.
How plyer works?
Plyer tries not to reinvent the wheel, and calls for external libraries to implement the api in the easiest way, depending on the current platform.
- On Android (python-for-android), pyjnius is used.
- On iOS (kivy-ios), pyobjus is used
- On windows/mac/linux, commonly found libraries and programs are used.
Let's look at the calling feature implementation for android platform. One thing to notice is this line of code from jnius import autoclass
where jnius is PyJnius and autoclass is a method that returns a Python class see code.
from jnius import autoclass
from plyer.facades import Call
from plyer.platforms.android import activity
Intent = autoclass('android.content.Intent')
uri = autoclass('android.net.Uri')
class AndroidCall(Call):
def _makecall(self, **kwargs):
intent = Intent(Intent.ACTION_CALL)
tel = kwargs.get('tel')
intent.setData(uri.parse("tel:{}".format(tel)))
activity.startActivity(intent)
def _dialcall(self, **kwargs):
intent_ = Intent(Intent.ACTION_DIAL)
activity.startActivity(intent_)
def instance():
return AndroidCall()
Using the above code to trigger calls.
>>> from plyer import call
>>> call.makecall('9997654321')
Internal working.
This sounds cool, how does plyer recognises the platform and uses only those files which are needed?
To understand this you need to dive into the codebase of plyer, first let us look at the file structure of plyer.
- plyer
- facades
- __init__.py
- ... abstract classes of each feature (https://www.python.org/dev/peps/pep-3119/)
way to abstract classes.
- platforms # Implementations.
- android/
- ios/
- linux/
- macosx/
- win/
- __init__.py
- __init__.py
- compat.py
- utils.py
- other files.
Let's take a look at one example of accelerometer and see how it works.
When we do from plyer import accelerometer
, since it's a module it goes to __init__.py
(link) file where the real magic happens.
# __init__.py file.
from plyer import facades
from plyer.utils import Proxy
#: Accelerometer proxy to :class:`plyer.facades.Accelerometer`
accelerometer = Proxy('accelerometer', facades.Accelerometer)
#other features...
This file creates a proxy for each feature available in facades/__init__.py
# utils.py file.
class Proxy(object):
...
def __init__(self, name, facade):
...
def _ensure_obj(self):
...
name = object.__getattribute__(self, '_name')
module = 'plyer.platforms.{}.{}'.format(
platform, name)
mod = __import__(module, fromlist='.')
obj = mod.instance()
where platform
is an object of Platform
class which is responsible to detect the platform name [Platform class internally usesfrom sys import platform
to get the platform's name.] and name
is the feature's name(accelerometer).
mod
is the module and instance is the method which returns the class defined in the accelerometer.py file under each platform's implementation.
The path: Goes to __init__.py
-> accelerometer = Proxy('accelerometer', facades.Accelerometer) -> Goes to Proxy class -> _ensure_obj
gets called -> object obj
becomes the object of the class from plyer.platforms.android.accelerometer
-> ready to import.
Contributing
Project Plyer is open to contributions visit https://github.com/kivy/plyer#contributing for more info.
Conclusion
Thank you for reading this post — I hope you found this helpful. You can find me on GitHub, LinkedIn and CodeMentor. If you have any questions, feel free to reach out to me!
More posts:
- Customizing your Navigation Drawer in Kivy & KivyMD
- Plyer: Platform independent compatibility layer
- Texture management in Kivy using atlas
- Beginner Kivy Tutorial: Basic Crash Course for Apps in Kivy
- Redux, Store, Actions, Reducers and logger: Get Started and a little further (1)
- Redux, Reactotron, Actions, Reducers and Sagas (2)