
Writing a to-do list in Python 3 for Android via QPython3 and SL4A
- Tutorial
The QPython engine (and QPython 3) for Android is still poorly studied, and especially with regard to its built-in Scripting Layer For Android (SL4A) library, it is also androidhelper. This library was written by several Google employees on the principle of 20% free time, provided it with Spartan documentation , which is almost impossible to find, and sent to free swimming. I searched bit by bit about the SL4A, but over time I found almost everything I need.
SL4A allows you to use almost all the features of the console Python 3 up to libraries like matplotlib, using the standard Android dialogs: text input, lists, questions, radio buttons, date selection, etc. The program will not amaze with beauty, but it will be able to solve many problems. Most importantly, we will get access to various functions of the device. For example, you can:
- make phone calls
- send SMS
- change volume
- turn on wifi and bluetooth
- open web pages
- open third-party applications
- take photos and videos with the camera
- extract contacts from the contact book
- send system alerts
- determine the GPS coordinates of the device
- detect battery
- read SIM card data
- play media
- work with clipboard
- generate voice messages
- export data to external activities (share)
- open local html pages
- and etc.
In our example, we will write the simplest list of tasks. We will be able to create and delete tasks, as well as export them. The program will vibrate and talk. We will use three types of dialogs: a list, text input, and a yes / no question. For everything about everything, less than 100 lines of code are enough for us. We’ll make the interface English for the sake of versatility (and GitHub).
Here is all the code and comments on the most significant points.
from androidhelper import Android
droid = Android()
We create an Android class droid object (), which will be responsible for interacting with SL4A.
path=droid.environment()[1]["download"][:droid.environment()[1]["download"].index("/Download")] + "/qpython/scripts3/tasks.txt"
The path variable will contain the absolute name of the file in which the tasks are stored. Why is it so long? The fact is that SL4A cannot work with the local path, so you have to determine the absolute, and the absolute can differ on different Android devices. We will get around this problem by locating the folder Download
using the method droid.environment()
. Then we clip Download
and add the path Qpython/Scripts3
(it is always the same) plus the file name.
def dialog_list(options):
droid.dialogCreateAlert("\ud83d\udcc3 My Tasks (%d)" % len(options))
droid.dialogSetItems(options)
droid.dialogSetPositiveButtonText("\u2795")
droid.dialogSetNegativeButtonText("Exit")
droid.dialogSetNeutralButtonText("\u2702")
droid.dialogShow()
return droid.dialogGetResponse()[1]
We define the function responsible for listing the tasks. This is done using the method droid.dialogCreateAlert()
. Then a number of helper methods display the actual items, create buttons and get the result from the user. The names of the two buttons are Unicode characters (more on that below). For simplicity, we will pack all these methods into one simple function, to which we will transfer the list of tasks. In more complex scripts, you can pass more arguments: title, button names, etc.
def dialog_text(default):
droid.dialogCreateInput("\u2795 New Task", "Enter a new task:", default)
droid.dialogSetPositiveButtonText("Submit")
droid.dialogSetNeutralButtonText("Clear")
droid.dialogSetNegativeButtonText("Cancel")
droid.dialogShow()
return droid.dialogGetResponse()[1]
We define the function responsible for creating a new task. The principle is similar. In the argument, default
we pass it the text that appears by default in the input line (empty with ""). In more complex programs, you can transfer various signatures and buttons.
def dialog_confirm(message):
droid.dialogCreateAlert("Confirmation", message)
droid.dialogSetPositiveButtonText("Yes")
droid.dialogSetNegativeButtonText("No")
droid.dialogShow()
return droid.dialogGetResponse().result
This function will ask the user a question to get a yes or no answer. We give her the text of the question.
while 1:
try:
with open(path) as file:
tasks=file.readlines()
except:
droid.makeToast("File %s not found or opening error" % path)
tasks=[]
We create a loop (so that the script does not exit after the first action) and the first thing we read is the task file and load it into the list tasks
. If the file does not exist, create an empty list.
response=dialog_list(tasks)
We list the tasks. When the user makes some choice, the method dialog_list()
returns this action as the value that we assign to the variable response
.
if "item" in response:
del tasks[response["item"]]
droid.vibrate(200)
droid.makeToast("Дело сделано!")
droid.ttsSpeak("Дело сделано!")
We begin to process the user action. Since the method droid.dialogGetResponse()
that we use in the list function produces a rather complex structure in the form of a dictionary, it will not have to be dissected in the most obvious way. In this case, by a simple click on a list item, it is deleted - we have completed the task. We will inform about this in a pop-up message and at the same time we will make (for fun) a vibrating alert for 200 milliseconds and generate a voice phrase Дело сделано!
.
elif "which" in response:
if "neutral" in response["which"]:
choice=dialog_confirm("Are you sure you want to wipe all tasks?")
if choice!=None and "which" in choice and choice["which"]=="positive":
tasks=[]
By pressing the middle (neutral) button with scissors, you can immediately delete all cases. A confirmation question will be displayed.
elif "positive" in response["which"]:
default=""
while 1:
input=dialog_text(default)
if "canceled" in input:
default=input["value"]
elif "neutral" in input["which"]:
default=""
elif "positive" in input["which"]:
tasks.append(input["value"]+"\n")
droid.ttsSpeak("Новое дело!")
break
else:
break
else:
exit=True
Here we create a new task. Let's pay attention to the variable cancel
- it gives out droid.dialogGetResponse()
in case of a click outside the dialog (on an empty area of the screen). In order to correctly handle this situation, we introduced an additional condition. The middle button ( neutral
) will clear the input field. When positive
we create a new list item and exit the loop. If you click on the right-most button, it works else
and we just exit the loop without saving anything (although formally this will be the value negative
in input["which"]
). The last line means that the user clicked on Exit
. Then we set the flag exit
to True
.
with open(path, "w") as file:
for i in range(len(tasks)): file.write(tasks[i])
After each processing of the list, we save the task list to a file.
if exit==True:
break
If the user decides to exit, we exit the main loop while
.
choice=dialog_confirm("Do you want to export tasks?")
if choice!=None and "which" in choice and choice["which"]=="positive":
droid.sendEmail("Email", "My Tasks", ''.join(tasks), attachmentUri=None)
At the very end, we ask the user whether it is necessary to export all the tasks somewhere - to mail, to the cloud, to the messenger, etc. If the answer is yes, the task list is converted to a string and exported.
That's all. The program will look like in the screenshot above.
Full listing
Final full listing (with comments in English):
#!/usr/bin/python
# -*- coding: utf-8 -*-
# This is a very simple to-do list for Android. Requires QPython3 to run (download it from Google Play Market).
from androidhelper import Android
droid = Android()
# Find absolute path on Android
path=droid.environment()[1]["download"][:droid.environment()[1]["download"].index("/Download")] + "/qpython/scripts3/tasks.txt"
def dialog_list(options):
"""Show tasks"""
droid.dialogCreateAlert("\ud83d\udcc3 My Tasks (%d)" % len(options))
droid.dialogSetItems(options)
droid.dialogSetPositiveButtonText("\u2795")
droid.dialogSetNegativeButtonText("Exit")
droid.dialogSetNeutralButtonText("\u2702")
droid.dialogShow()
return droid.dialogGetResponse()[1]
def dialog_text(default):
"""Show text input"""
droid.dialogCreateInput("\u2795 New Task", "Enter a new task:", default)
droid.dialogSetPositiveButtonText("Submit")
droid.dialogSetNeutralButtonText("Clear")
droid.dialogSetNegativeButtonText("Cancel")
droid.dialogShow()
return droid.dialogGetResponse()[1]
def dialog_confirm(message):
"""Confirm yes or no"""
droid.dialogCreateAlert("Confirmation", message)
droid.dialogSetPositiveButtonText("Yes")
droid.dialogSetNegativeButtonText("No")
droid.dialogShow()
return droid.dialogGetResponse().result
# Run main cycle
while 1:
# Open file
try:
with open(path) as file:
tasks=file.readlines()
except:
droid.makeToast("File %s not found or opening error" % path)
tasks=[]
# Show tasks and wait for user response
response=dialog_list(tasks)
# Process response
if "item" in response: # delete individual task
del tasks[response["item"]]
droid.vibrate(200)
droid.makeToast("Дело сделано!")
droid.ttsSpeak("Дело сделано!")
elif "which" in response:
if "neutral" in response["which"]: # delete all tasks
choice=dialog_confirm("Are you sure you want to wipe all tasks?")
if choice!=None and "which" in choice and choice["which"]=="positive":
tasks=[]
elif "positive" in response["which"]: # create new task
default=""
while 1:
input=dialog_text(default)
if "canceled" in input:
default=input["value"]
elif "neutral" in input["which"]: # clear input
default=""
elif "positive" in input["which"]: # create new task
tasks.append(input["value"]+"\n")
droid.ttsSpeak("Новое дело!")
break
else:
break
else:
exit=True
# Save tasks to file
with open(path, "w") as file:
for i in range(len(tasks)): file.write(tasks[i])
# If user chose to exit, break cycle and quit
if exit==True:
break
# Export tasks
choice=dialog_confirm("Do you want to export tasks?")
if choice!=None and "which" in choice and choice["which"]=="positive":
droid.sendEmail("Email", "My Tasks", ''.join(tasks), attachmentUri=None)
You can also find it on GitHub .
A couple of comments. SL4A does not allow the use of any graphics, however, you can use a fairly large number of various smiles and emoji as Unicode characters. It can be at least houses, at least dogs, at least cats. In our example, we used a plus sign ( \u2795
), scissors ( \u2702
) and a piece of paper ( \ud83d\udcc3
). With each new version of Unicode, there are more and more of them, but this should not be abused - new emoticons will not be displayed on older versions of Android.
To run QPython scripts, you need to go into QPython itself, but there is an interesting plugin for the Tasker application that allows you to do quite powerful things with QPython scripts, for example, displaying them on the desktop in the form of icons or launching them under various conditions.
Related Resources
- QPython 3 on the Google Play Market
- QPython Website
- Androidhelper documentation
- SL4A on GitHub
- A little description of SL4A in Python Central
- The blog of a man sometimes writing about SL4A with examples
PS Questions and comments are better to write to me in PM.