How to create a simple To-do list application with Kivymd

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()
    Running our code so far:
    Todo sample
    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:
    Todo sample 2
    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.0.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.

    23

    This website collects cookies to deliver better user experience

    How to create a simple To-do list application with Kivymd