Accessing Kivy Screenmanagaer inside a screen's __init__ method

Question:

I am trying to build an application that loads data from a JSON file and adds it to an MDList object. I would like the loaded items to take the user to a specific page when clicked. My implementation of the __init__ finction is shown bellow:

def __init__(self, **kw):
        super().__init__(**kw)
        json_data_object = JsonData("data.json")
        # adds list item from JSON file to MDlist object 
        for i in json_data_object.data["lists"]:
            loaded_object = ListItemWithoutCheckbox(text="[b]" + i["list_name"] + "[/b]")
            self.ids["Container"].add_widget(loaded_object)
            
            self.manager.add_widget(ToDoListPage(name=str(loaded_object.text)))
            loaded_object.bind(on_release= lambda x : self.change_screen(loaded_object.text))

The first half of the for loop works as intended, adding the loaded objects to the MDList object. However the second half returns an AttributeError:

AttributeError: 'NoneType' object has no attribute 'add_widget'

I have a theory that this is due to the __init__ function running before the screen is added to the ScreenManager() object in the MainApp() class, shown below, but have no concrete proof of this nor any ideas for how to get around the issues.

class MainApp(MDApp):
    def build(self):
        # Setting theme to my favorite theme
        self.theme_cls.theme_style = "Dark"
        Builder.load_file("styling.kv")
        sm = ScreenManager()
        sm.add_widget(ToDoListView(name="ListView"))
        return sm

I will keep trying to work on the issue but I am struggling to come up with new ideas, so any help is greatly appreciated.

If I manage to crack this I will post my solution to this issue!

Thanks for any help 🙂

EDIT:

I have added a method to attempt to add the on_release functionality after the __init__ method has run. On printing self.parent I still receive a None value. I have also attempted to use the Clock module of kivy as suggested in the comments by John Anderson but I am still receiving the same AttributeError shown earlier in this question. My edited code now looks like this:

Class initialisation:

def __init__(self, **kw):
        super().__init__(**kw)
        json_data_object = JsonData("data.json")
        self.loaded_items = []
        # adds list item from JSON file to MDlist object 
        for i in json_data_object.data["lists"]:
            loaded_object = ListItemWithoutCheckbox(text="[b]" + i["list_name"] + "[/b]")
            self.ids["Container"].add_widget(loaded_object)
            
            self.loaded_items.append(loaded_object)

    def load_tasks(self, loaded_objects):
        for i in loaded_objects:
            # To test if i can access the screen manager
            print(self.parent)
            self.manager.add_widget(ToDoListPage(name=str(i.text)))
            object.bind(on_release= lambda x : self.change_screen(i.text))

Main app class:

class MainApp(MDApp):
    def build(self):
        # Setting theme to my favorite theme
        self.theme_cls.theme_style = "Dark"
        Builder.load_file("styling.kv")
        list_view_screen = ToDoListView(name = "ListView")
        list_view_screen.load_tasks(list_view_screen.loaded_items)
        sm = ScreenManager()
        sm.add_widget(list_view_screen)
        return sm

Thank you so much for even checking out this question!

any help is greatly appreciated 🙂

Asked By: Dean Williams

||

Answers:

happy to say i finally found a solution to the issue. By abstracting the functionality of the __init__ method into another method and making a partial call in kivy’s Clock object, the function could utilise the screen manager object.

def __init__(self, sm,**kw):
        super().__init__(**kw)
        Clock.schedule_once(partial(self.load_tasks, sm)) 

    def load_tasks(self, sm, *largs):
        json_data_object = JsonData("data.json")
        self.loaded_items = []

        for i in json_data_object.data["lists"]: 
            self.add_loaded_item_to_list(i)    

        for i in self.loaded_items:
            self.bind_on_release_to_loaded_item(sm, i) 

    def bind_on_release_to_loaded_item(self, sm, loaded_item):
        self.manager.add_widget(ToDoListPage(name = loaded_item.text))
        loaded_item.bind(on_release= lambda x: self.change_screen(loaded_item.text))
    
    def add_loaded_item_to_list(self, loaded_item):
            loaded_object = ListItemWithoutCheckbox(text="[b]" + loaded_item["list_name"] + "[/b]")
            self.ids["Container"].add_widget(loaded_object)
            self.loaded_items.append(loaded_object)

The MainApp class reverted to its original:

class MainApp(MDApp):
    def build(self):
        # Setting theme to my favorite theme
        self.theme_cls.theme_style = "Dark"
        Builder.load_file("styling.kv")
        sm = ScreenManager()
        sm.add_widget(ToDoListView(sm,name = "ListView"))
        return sm

Thanks again to John Anderson for helping me get to this solution 🙂

Answered By: Dean Williams
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.