Ngonidzashe Nzenze
Posted on October 19, 2021
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
Posted on October 19, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.