To-do list applications are a simple way to get started with learning different frameworks. I am going show you how to create one. With that, let's look at what the final application looks like on an android device:
Developing the application
Make sure your have installed kivy and kivymd in a virtual environment.
Create 3 files in the same directory, namely:
-
main.py
- will to contain most of the application code and logic. -
main.kv
- will contain code to display the interface. -
database.py
- will contain all the database code.
Inside main.py
, add the following code:
#main.py
from kivymd.app import MDApp
class MainApp(MDApp):
def build(self):
# Setting theme to my favorite theme
self.theme_cls.primary_palette = "DeepPurple"
if __name__ == '__main__':
app = MainApp()
app.run()
In main.kv
add the following code:
#main.kv
MDFloatLayout:
MDLabel:
id: task_label
halign: 'center'
markup: True
text: "[u][size=48][b]My Tasks[/b][/size][/u]"
pos_hint: {'y': .45}
ScrollView:
pos_hint: {'center_y': .5, 'center_x': .5}
size_hint: .9, .8
MDList:
id: container
MDFloatingActionButton:
icon: 'plus-thick'
on_release: app.show_task_dialog() #functionality to be added later
elevation_normal: 12
pos_hint: {'x': .8, 'y':.05}
If you run the application right now, you will get something like this:
Add Tasks
Next we are going to create a dialog box in which we will be able to add tasks. The dialog box will allow us to enter the task name and completion date:
main.py
#main.py
# add the following imports
from kivymd.uix.dialog import MDDialog
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.picker import MDDatePicker
from datetime import datetime
class DialogContent(MDBoxLayout):
"""OPENS A DIALOG BOX THAT GETS THE TASK FROM THE USER"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
# set the date_text label to today's date when useer first opens dialog box
self.ids.date_text.text = str(datetime.now().strftime('%A %d %B %Y'))
def show_date_picker(self):
"""Opens the date picker"""
date_dialog = MDDatePicker()
date_dialog.bind(on_save=self.on_save)
date_dialog.open()
def on_save(self, instance, value, date_range):
"""This functions gets the date from the date picker and converts its it a
more friendly form then changes the date label on the dialog to that"""
date = value.strftime('%A %d %B %Y')
self.ids.date_text.text = str(date)
Now change the MainApp
class inside main.py
to look like this:
# main.py
#...
class MainApp(MDApp):
task_list_dialog = None # Here
def build(self):
# Setting theme to my favorite theme
self.theme_cls.primary_palette = "DeepPurple"
# Add the below functions
def show_task_dialog(self):
if not self.task_list_dialog:
self.task_list_dialog = MDDialog(
title="Create Task",
type="custom",
content_cls=DialogContent(),
)
self.task_list_dialog.open()
def close_dialog(self, *args):
self.task_list_dialog.dismiss()
def add_task(self, task, task_date):
'''Add task to the list of tasks'''
print(task.text, task_date)
task.text = '' # set the dialog entry to an empty string(clear the text entry)
Now modify main.kv
:
# main.kv
#...
# add the following
<DialogContent>:
orientation: "vertical"
spacing: "10dp"
size_hint: 1, None
height: "130dp"
GridLayout:
rows: 1
MDTextField:
id: task_text
hint_text: "Add Task..."
pos_hint: {"center_y": .4}
max_text_length: 50
on_text_validate: (app.add_task(task_text, date_text.text), app.close_dialog())
MDIconButton:
icon: 'calendar'
on_release: root.show_date_picker()
padding: '10dp'
MDLabel:
spacing: '10dp'
id: date_text
BoxLayout:
orientation: 'horizontal'
MDRaisedButton:
text: "SAVE"
on_release: (app.add_task(task_text, date_text.text), app.close_dialog())
MDFlatButton:
text: 'CANCEL'
on_release: app.close_dialog()
Now we want to add list items to the screen. We are going to create a custom list item with a checkbox to the left and a delete icon to the right:
main.py
# main.py
#...
# Add these imports
from kivymd.uix.list import TwoLineAvatarIconListItem, ILeftBodyTouch
from kivymd.uix.selectioncontrol import MDCheckbox
# create the following two classes
class ListItemWithCheckbox(TwoLineAvatarIconListItem):
'''Custom list item'''
def __init__(self, pk=None, **kwargs):
super().__init__(**kwargs)
# state a pk which we shall use link the list items with the database primary keys
self.pk = pk
def mark(self, check, the_list_item):
'''mark the task as complete or incomplete'''
if check.active == True:
# add strikethrough to the text if the checkbox is active
the_list_item.text = '[s]'+the_list_item.text+'[/s]'
else:
# we shall add code to remove the strikethrough later
pass
def delete_item(self, the_list_item):
'''Delete the task'''
self.parent.remove_widget(the_list_item)
class LeftCheckbox(ILeftBodyTouch, MDCheckbox):
'''Custom left container'''
Modify the add_task
function in the MainApp
class:
# main.py
#...
class MainApp(MDApp):
#...
def add_task(self, task, task_date):
'''Add task to the list of tasks'''
print(task.text, task_date)
self.root.ids['container'].add_widget(ListItemWithCheckbox(text='[b]'+task.text+'[/b]', secondary_text=task_date))
task.text = '' # set the dialog entry to an empty string(clear the text entry)
main.kv
# main.kv
# add the following code
<ListItemWithCheckbox>:
id: the_list_item
markup: True
LeftCheckbox:
id: check
on_release:
root.mark(check, the_list_item)
IconRightWidget:
icon: 'trash-can-outline'
theme_text_color: "Custom"
text_color: 1, 0, 0, 1
on_release:
root.delete_item(the_list_item)
Running the application so far:
Ok, now to work on the code for the database. Inside database.py
add the following code:
#database.py
import sqlite3
class Database:
def __init__(self):
self.con = sqlite3.connect('todo.db')
self.cursor = self.con.cursor()
self.create_task_table() #create the tasks table
def create_task_table(self):
"""Create tasks table"""
self.cursor.execute("CREATE TABLE IF NOT EXISTS tasks(id integer PRIMARY KEY AUTOINCREMENT, task varchar(50) NOT NULL, due_date varchar(50), completed BOOLEAN NOT NULL CHECK (completed IN (0, 1)))")
self.con.commit()
def create_task(self, task, due_date=None):
"""Create a task"""
self.cursor.execute("INSERT INTO tasks(task, due_date, completed) VALUES(?, ?, ?)", (task, due_date, 0))
self.con.commit()
# GETTING THE LAST ENTERED ITEM SO WE CAN ADD IT TO THE TASK LIST
created_task = self.cursor.execute("SELECT id, task, due_date FROM tasks WHERE task = ? and completed = 0", (task,)).fetchall()
return created_task[-1]
def get_tasks(self):
"""Get all completed and uncomplete tasks"""
uncomplete_tasks = self.cursor.execute("SELECT id, task, due_date FROM tasks WHERE completed = 0").fetchall()
completed_tasks = self.cursor.execute("SELECT id, task, due_date FROM tasks WHERE completed = 1").fetchall()
# return the tasks to be added to the list when the application starts
return completed_tasks, uncomplete_tasks
def mark_task_as_complete(self, taskid):
"""Mark tasks as complete"""
self.cursor.execute("UPDATE tasks SET completed=1 WHERE id=?", (taskid,))
self.con.commit()
def mark_task_as_incomplete(self, taskid):
"""Mark task as uncomplete"""
self.cursor.execute("UPDATE tasks SET completed=0 WHERE id=?", (taskid,))
self.con.commit()
# return the task text
task_text = self.cursor.execute("SELECT task FROM tasks WHERE id=?", (taskid,)).fetchall()
return task_text[0][0]
def delete_task(self, taskid):
"""Delete a task"""
self.cursor.execute("DELETE FROM tasks WHERE id=?", (taskid,))
self.con.commit()
def close_db_connection(self):
self.con.close()
The code above allows us to create, delete and modify tasks in the database.
Now to join this with the application interface:
main.py
#main.py
#...
# add import
from database import Database
# Initialize db instance
db = Database()
# Modify the ListItemWithCheckbox class
class ListItemWithCheckbox(TwoLineAvatarIconListItem):
#...
def mark(self, check, the_list_item):
'''mark the task as complete or incomplete'''
if check.active == True:
the_list_item.text = '[s]'+the_list_item.text+'[/s]'
db.mark_task_as_complete(the_list_item.pk)# here
else:
the_list_item.text = str(db.mark_task_as_incomplete(the_list_item.pk))# Here
def delete_item(self, the_list_item):
'''Delete the task'''
self.parent.remove_widget(the_list_item)
db.delete_task(the_list_item.pk)# Here
# Modify the MainApp class
class MainApp(MDApp):
#...
# add this entire function
def on_start(self):
"""Load the saved tasks and add them to the MDList widget when the application starts"""
try:
completed_tasks, uncomplete_tasks = db.get_tasks()
if uncomplete_tasks != []:
for task in uncomplete_tasks:
add_task = ListItemWithCheckbox(pk=task[0],text=task[1], secondary_text=task[2])
self.root.ids.container.add_widget(add_task)
if completed_tasks != []:
for task in completed_tasks:
add_task = ListItemWithCheckbox(pk=task[0],text='[s]'+task[1]+'[/s]', secondary_text=task[2])
add_task.ids.check.active = True
self.root.ids.container.add_widget(add_task)
except Exception as e:
print(e)
pass
# Modify the add_task function
def add_task(self, task, task_date):
'''Add task to the list of tasks'''
# Add task to the db
created_task = db.create_task(task.text, task_date)# Here
# return the created task details and create a list item
self.root.ids['container'].add_widget(ListItemWithCheckbox(pk=created_task[0], text='[b]'+created_task[1]+'[/b]', secondary_text=created_task[2]))# Here
task.text = ''
And with that, we're done!
Packaging for android
I have included all the code on github, including the spec file I used to generate the apk.
A few changes are required so that we can create an android application. Edit main.py
as follows:
#...
from kivymd.uix.pickers import MDDatePicker # Here, instead of kivymd,uix.picker
# add the following just under the imports
if platform == "android":
from android.permissions import request_permissions, Permission
request_permissions([Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE])
The above code will prompt the user to allow the application to access storage.
Main changes in the buildozer spec file are as follows:
requirements = python3, kivy==2.1.0, https://github.com/kivymd/KivyMD/archive/master.zip,sdl2_ttf==2.0.15,pillow,android
And
android.permissions = WRITE_EXTERNAL_STORAGE
That's all for this tutorial. I hope you enjoyed it.
Cover Photo by Glenn Carstens-Peters on Unsplash
Top comments (20)
Learned a lot from your sample app here. Now, may I ask you if I can modify-tinker with your program a little bit and integrate it with a few small apps into one single app for Android (for personal use only)? Thanks.
I am glad you learned something. Please make use of it as you see appropriate. You're welcome.
Tysm for such an amazing todo list
checking whether the C compiler works... no
configure: error: in
/content/.buildozer/android/platform/build-arm64-v8a_armeabi-v7a/build/other_builds/freetype/armeabi-v7a__ndk_target_21/freetype/builds/unix':
config.log' for more detailsconfigure: error: C compiler cannot create executables
See
make: *** [builds/unix/detect.mk:91: setup] Error 77
how can i resolve this
It looks like the problems is that you have in your buildozer.spec the target architecture like this: "android.archs = arm64-v8a, armeabi-v7a" and then buildozer cannot find the sources to install in order to compile the packages from the "requirements=" option for this architecture "armeabi-v7a", the easiest way to solve this issue is to remove that architecture and compile for "arm64-v8a" only since the armeabi-v7a or arm-v7a it's already deprecated.
hey YOU!!! new guy!!!!
WE welcome you here by force. with love.
You like it or not you shall be loved here at dev.to. Loved and Loved Dearly.
WElcome.
I'm running this great example you made. Unfortunately, the requirements you specified are no longer valid. What do I need to change in the Buildozer requirements to make it work. Thank you
Glad you liked the article! I'll update the article soon.
I've managed to use the following requirements to get it to run successfully:
requirements = python3,kivy==2.1.0,kivymd==1.1.1,sdl2_ttf==2.0.15,pillow,android
Hope that helps
Does anybody knows how to make this function work with KivyMD 2.0.1.dev0?
def show_task_dialog(self):
if not self.task_list_dialog:
self.task_list_dialog = MDDialog(
cls=DialogContent(),
)
self.task_list_dialog.open()
Like this above it results an error before to run the app:
Error: 'MainApp' object has no attribute 'task_list_dialog'
So I changed to this:
def show_task_dialog(self):
self.task_list_dialog = MDDialog(
cls=DialogContent(),
)
self.task_list_dialog.open()
Then it runs but it results in another error when called by:
on_release: app.show_task_dialog()
The console log bellow:
self.task_list_dialog = MDDialog(
^^^^^^^^^
File "kivy/event.pyx", line 262, in kivy._event.EventDispatcher.init_
File "kivy/properties.pyx", line 520, in kivy.properties.Property.set
File "kivy/properties.pyx", line 938, in kivy.properties.ListProperty.set
File "kivy/properties.pyx", line 815, in kivy.properties.ObservableList.init
TypeError: 'DialogContent' object is not iterable
Any idea how to fix it? thank you.
Thanks for this amazing project. I wanted to ask you a question: what exactly should I modify to change the Check Boxes with Radio Buttons? I've really tried everything, but without getting what I want. Thank you.
Hi,
The check boxes in kivymd work as radio buttons if you group them. Check out this documentation for more details. I hope it will be helpful.
It is a great post. I have tried the same steps. Everything works fine, but while converting to apk and after installing in android app it is showing loading and closes the app. Any way to resolve that. I followed the proper steps and created the .apk. Please can you guide me as I am in a learning phase using python.
Hello. I'm glad you liked the post. The best way to figure out why your app is crashing on android is to install it using ADB(android debug bridge). This will allow you to trace the error encountered by your program during execution. If you have never used ADB before, check out this article(the very bottom section), in which I go over the basics of how you can use it. I hope that helps :)
Really nice and helpful post you nailed it .
I have saved it
Keep it up
Thanks a lot
Good Tuto even one year later ! liked it a lot ! hopefully kivy could be used as a good alternative to ionic & capacitor, java, native script frameworks family or even kotlin ... hhh
Thank for this tutorial. Nice written