📜 ⬆️ ⬇️

Writing a to-do list in Python 3 for Android via QPython3 and SL4A


The QPython (and QPython 3) engine for Android is still poorly understood, and especially with its built-in library Scripting Layer For Android (SL4A), which is also androidhelper. This library was written by several Google employees on the principle of 20% of free time, supplied it with Spartan documentation , which is almost impossible to find, and sent to free navigation. I was looking for information about SL4A bit by bit, but over time I found almost everything I need.


SL4A allows you to use almost all the capabilities of the console Python 3, up to matplotlib-type libraries, using the standard Android dialogs: text input, lists, questions, radio buttons, date selection, etc. The program will not be amazing with beauty, but it will be able to solve many problems. The most important thing is that we will get access to various functions of the device. For example, you can:



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: list, text input and the question "yes / no." For everything about everything, we need less than 100 lines of code. The interface is done in English for the sake of versatility (and GitHub).


Here are all the code and comments on the most significant points.


from androidhelper import Android droid = Android() 

Create a droid object of the Android class (), which will be responsible for the interaction 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 tasks are stored. Why is it so long? The fact is that SL4A cannot work with a local path, so you have to determine the absolute, and the absolute can differ on different Android devices. We will work around this problem by locating the Download folder using the droid.environment() method. Then we cut off the Download and add the 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 displaying the list of tasks. This is done using the droid.dialogCreateAlert() method. Then a number of auxiliary methods derive the items themselves, create buttons and get the result from the user. The names of the two buttons are Unicode characters (more on this below). To simplify, we will pack all these methods into one simple function, to which we will transmit a list of tasks. In more complex scripts, more arguments can be passed: 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 default argument, we pass to it the text, which by default appears 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 feature will ask the user a question in order 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 cycle (so that the script does not exit after the first action) and first of all we read the task file and load it into the tasks list. If there is no file, create an empty list.


 response=dialog_list(tasks) 

Display a list of tasks. When a user makes a choice, the dialog_list() method returns this action as a value, which we assign to the response variable.


 if "item" in response: del tasks[response["item"]] droid.vibrate(200) droid.makeToast(" !") droid.ttsSpeak(" !") 

We start to process the user action. Since the droid.dialogGetResponse() method, which we use in the list function, produces a rather complex structure in the form of a dictionary, it will have to be prepared in a non-obvious way. In this case, by simply clicking on a list item, it is deleted - we have completed the work. We will inform about this in a pop-up message and at the same time make (purely 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 delete all cases at once. This will display a confirmatory question.


 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. Pay attention to the variable cancel - it is issued by droid.dialogGetResponse() in the case of a click outside the dialog (on an empty area of ​​the screen). To correctly handle this situation, we have introduced an additional condition. The middle button ( neutral ) will clear the input field. With positive we create a new list item and exit the loop. If you click on the rightmost button, it will work else and we will simply exit the cycle without saving anything (although formally this will be the value negative in input["which"] ). The last line indicates that the user has clicked Exit . Then we set the exit flag to True .


 with open(path, "w") as file: for i in range(len(tasks)): file.write(tasks[i]) 

After each list processing, save the task list to a file.


 if exit==True: break 

If the user decides to exit, we exit the main 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 positive, 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 you to use any graphics, but you can use a fairly large number of various emoticons and emoji as Unicode characters. It may be even houses, even dogs, even cats. In our example, we used the plus sign ( \u2795 ), scissors ( \u2702 ), and a piece of paper ( \ud83d\udcc3 ). With each new version of Unicode, they are becoming more and more, but this should not be abused - new smiles will not be displayed on older versions of Android.


To run QPython scripts, you need to log in to 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 as icons or running under various conditions.


Useful resources on the topic



PS Questions and comments better to write to me in PM.


')

Source: https://habr.com/ru/post/341580/


All Articles