KivyMD MDTimePicker is not returning time, MDTimePicker returns None

Question:

I’m trying to make a program where the program makes the user pick a due date and time for their tasks. User presses a button to open a dialog that takes task name, description and due date-time from them. Everything is fine, except I couldn’t make the get_time() function do anything other than return time. And it returns None instead of the picked time. I made a seperate test app just to test if the picker works and it does. It can change the text of a label too, not just return time.

main.py

from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.uix.dialog import MDDialog
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.picker import MDDatePicker, MDTimePicker
from datetime import datetime
from kivymd.uix.list import TwoLineListItem

class MainApp(MDApp):
    
    task_list_dialog = None
    
    def build(self):
        return Builder.load_file('main.kv')
    
    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, description, task_date):
        self.root.get_screen("testwin").ids['container'].add_widget(ListItem(text="[size=18]"+task.text+"[/size]", secondary_text=task_date))
        print(task.text+"n", description.text+"n", task_date)
        task.text = '' # set the dialog entry to an empty string(clear the text entry)
        description.text = ''


class DialogContent(MDBoxLayout):
    
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.ids.date_text.text = str(datetime.now().strftime('%A %d %B %Y, %H:%M'))

    def show_date_picker(self):
        date_dialog = MDDatePicker()
        date_dialog.bind(on_save=self.on_save)
        date_dialog.open()

    def on_save(self, instance, value, date_range):
        date = value.strftime('%A %d %B %Y')
        time = self.show_time_picker()
        
        self.ids.date_text.text = date+str(time)

    def show_time_picker(self):
        time_dialog = MDTimePicker()
        time_dialog.bind(time=self.get_time)
        time_dialog.open()
    
    def get_time(self, instance, time):
        return time


class ListItem(TwoLineListItem):
    def __init__(self, pk=None, **kwargs):
        super().__init__(**kwargs)
        self.pk = pk
 

class WindowManager(ScreenManager):
    pass

class TestWindow(Screen):
    pass

if __name__ == "__main__":
    MainApp().run()

main.kv

#: include testwindow.kv

WindowManager:
    TestWindow:

testwindow.kv

<TestWindow>:
    name: "testwin"
    Screen:
        BoxLayout:
            orientation: "vertical"
            
            BoxLayout:
                orientation: 'vertical'    
                ScrollView:
                    do_scroll_x: False
                    do_scroll_y: True
                    pos_hint: {"center_x": 0.5, "center_y": 0.5}
                    size_hint: 1, 0.7

                    MDList:
                        id: container
                    
                MDFloatingActionButton:
                    icon: 'plus-thick'
                    on_release: app.show_task_dialog()
                    elevation_normal: 10
                    pos_hint: {'x': 0.82, 'y': 0.07}


<DialogContent>:
    orientation: "vertical"
    spacing: "10dp"
    size_hint: 1, None
    height: "420dp"

    BoxLayout:
        orientation: 'vertical'

        MDTextField:
            id: task_text
            hint_text: "Task"
            on_text_validate: (app.add_task(task_text, task_desc, date_text.text), app.close_dialog())

        MDTextField:
            id: task_desc
            mode: "fill"
            multiline: True
            hint_text: "Task Description"
            size_hint_y: 0.1

    BoxLayout:
        orientation: 'horizontal'
        size_hint_y: 0.1
        MDIconButton:
            icon: 'calendar'
            on_release: root.show_date_picker()
            padding: '10dp'

        MDLabel:
            spacing: '10dp'
            font_size: 15
            id: date_text
        
        
    BoxLayout:
        orientation: 'horizontal'
        size_hint_y: 0.1

        MDRaisedButton:
            id: savetask
            text: "SAVE"
            on_release: (app.add_task(task_text, task_desc, date_text.text), app.close_dialog())
        MDFlatButton:
            id: canceltask
            text: 'CANCEL'
            on_release: app.close_dialog()


<ListItem>:
    id: the_list_item
    markup: True
    multiline: True
Asked By: mkfmtr

||

Answers:

The problem is that in method on_save you assigned the returned value of the method show_time_picker (which is clearly None) to a var. time. That’s why you never get the time value (instead of that None).

One of many ways to achieve that is as follows:

First create class attributes to store the desired values. This way you will be able to separate attrs. from methods and access those attrs. anywhere in your code.

class DialogContent(MDBoxLayout):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # Create new attributes to save desired data later.
        self._time = None
        self._data = None
        self.ids.date_text.text = str(datetime.now().strftime('%A %d %B %Y, %H:%M'))

    def on_save(self, instance, value, date_range):
        # Assign the date value hare.
        self._date = value.strftime('%A %d %B %Y')
        # Open time picker.
        self.show_time_picker()


    def show_time_picker(self):
        time_dialog = MDTimePicker()
#        time_dialog.bind(time=self.get_time)
        time_dialog.bind(on_save=self.get_time)
        time_dialog.open()

    # Here's a conventional trick for checking what 'args' or 'kwargs' are used by some method.
    # For that, define that function with variable no. of args and kwargs and print those.
    # def get_time(self, *args, **kwargs):
        # print(args, kwargs)
        # As you see that should be two args, 'MDTimePicker' object and the 'time' value but with no kwargs.
        # So you can write it as,
    def get_time(self, instance, time):
        # Assign the time value here.
        self._time = time
        # Now set date and time.
        self.ids.date_text.text = self._date+str(self._time)

Note:

Since in method show_time_picker you bind a kivy property time (of MDTimePicker) to a method (get_time), so this method (get_time) will only be called whenever the value (time) changes. In simple words, say, the user just opens and then closes the time-picker without modifying the time, then as the value has not changed, the default value (None) will be used.

That’s why it is safe to use (by forcing the user to use ok button) another default method on_save.

Answered By: ApuCoder
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.