Why are widgets not created at __init__ function?

I'm new to kivy so I'm trying to do an App, useful to me, to learn this framework.

My need is to manage my shift work schedule so I started inserting calendar days into a GridLayout. After several troubles now I'm blocked because I've got this error:

File "turno.py", line 53, in load_content    
  self.gridMonthView.add_widget(DaysInfo(day=wid))  
AttributeError: 'NoneType' object has no attribute 'add_widget'

The load_content is called from inside the __init__ function, the error happens when I try to add DaysInfo widget (one for each day of the current month).

The strange (for me) behaviour is that if I comment line n.47 (that calls the load_content method) the program goes running and then I'm able to use the load_content from inside functions prevMonth and nextMonth without having any error.

So the question is:

  • Why does it seem that inside the __init__ method I can't use add_widget for gridMonthView reference/object but it's possible from the other methods of the same class?

  • Maybe that is not possible to add widget before the __init__ function ends something that I don't know/understand?

So, here's the code:

  1. A little module to handle dates: calendario.py

    from calendar import Calendar
    from datetime import date
    
    IT_months = ['Gennaio', 'Febbraio', 'Marzo', 'Aprile',\
                  'Maggio', 'Giugno', 'Luglio', 'Agosto',\
                  'Settembre', 'Ottobre', 'Novembre', 'Dicembre']
    
    IT_days = ['Lunedi',
               'Martedi',
               'Mercoledi',
               'Giovedi',
               'Venerdi',
               'Sabato',
               'Domenica']
    
    
    class Calendario():
    
        def __init__ (self):
    
            self.currDay = date.today()
            self.calen = Calendar(0)
            # def __init__ (self):
    
        def getMonth(self):
            return IT_months[self.currDay.month-1]
            # def getMonth(self):
    
        def getYear(self):
            return str(self.currDay.year)
            # def getYear(self):
    
        def getDaysOfMonth(self, listOfDays):
            listOfDays = []
            for td in self.calen.itermonthdates(self.currDay.year, self.currDay.month):
                listOfDays.append(td.day)
                # for td in ...
    
            return listOfDays
            # def getDaysOfMonth(self, listOfDays):
    
        def setNextMonth(self):
            if self.currDay.month == 12:
                self.currDay = self.currDay.replace(month=1, year=(self.currDay.year+1))
            else:
                self.currDay = self.currDay.replace(month=(self.currDay.month+1))
            # def setNextMonth(self):
    
        def setPrevMonth(self):
            if self.currDay.month == 1:
                self.currDay = self.currDay.replace(month=12, year=(self.currDay.year-1))
            else:
                self.currDay = self.currDay.replace(month=(self.currDay.month-1))
            # def setNextMonth(self):
    
  2. the .kv file: turno.kv

    #:kivy 1.9.0
    # 
    # menu bar
    #
    <MenuBar>:
        orientation: 'horizontal'
        padding: 1
        spacing: 1
        size_hint_y: 0.15
        Button:
            text: "Icon"
            size_hint_x: 0.3
            on_press: root.menu()
        Button:
            text: "Title"
            #size_hint: 1, 0.5
    
    
    #
    # day's info 
    #
    <DaysInfo>:
        orientation: 'vertical'
        padding: 1
        spacing: 1
        Label:
            color: 1,0,0,1
            background_color: 1,1,1,1
            id: f1
            text: " "
        Label:
            id: f2
            text: " "
        Label:
            id: f3
            text: " "
    
    
    #
    # month view
    #
    <MonthView>:
        gridMonthView: gridMonthView
        #
        orientation: "vertical"
        #
        # month selection
        #
        BoxLayout:
            id: box1
            orientation: 'horizontal'
            padding: 1
            spacing: 1
            size_hint_y: 0.15
            Button:
                backgroud_color: 0,1,0,1
                text: " << "
                size_hint_x: 0.1
                on_press: root.prevMonth()
            Button:
                id: idSelMonth
                text: root.curMonth
                size_hint_x: 0.5
            Button:
                id: isSelYear
                text: root.curYear
                size_hint_x: 0.3
            Button:
                text: " >> "
                size_hint_x: 0.1
                on_press: root.nextMonth()
        #
        # week's days
        #
        BoxLayout:
            id: box2
            orientation: 'horizontal'
            padding: 1
            spacing: 1
            color: 0., 0.5, 0.5, 1
            size_hint_y: 0.1
            Label:
                text: "Lu"
            Label:
                text: "Ma"
            Label:
                text: "Me"
            Label:
                text: "Gi"
            Label:
                text: "Ve"
            Label:
                text: "Sa"
            Label:
                text: "Do"
        #
        # Month's days
        #
        GridLayout:
            id: gridMonthView
            cols: 7
            rows: 6
            padding: 1
            spacing: 1
            size_hint_y: 0.6
    
    #
    # Turno Main Form
    #
    <TurnoMainForm>:
        orientation: 'vertical'
        padding: 20
        spacing: 10
        #width: 400
        #height: 800
    
        # menu bar
        MenuBar:
    
        # month view
        MonthView:
            id: id1
    
  3. the app's source code: turno.py

    # -*- coding: utf-8 -*-
    """
    Created on Mon Oct 06 12:25:04 2015
    
    @author: a809077
    """
    
    from kivy.app import App
    from kivy.uix.boxlayout import BoxLayout
    from kivy.uix.gridlayout import GridLayout
    from kivy.properties import ObjectProperty, StringProperty
    from kivy.config import Config
    
    from calendario import Calendario
    
    # impostazione della grandezza della finestra
    Config.set('graphics', 'width', '480')
    Config.set('graphics', 'height', '800')
    
    class MenuBar(BoxLayout):
    
        def __init__(self, **kwargs):
            super(MenuBar, self).__init__(**kwargs)
            print ("------- MenuBar -------")
            for child in self.children:
                print(child)
    
        def menu (self):
            print (" ====== click sul menu ======")
        # end of class: MenuBar(BoxLayout)
    
    ##
    ## visualizza i dati mensili    
    ##
    class MonthView(BoxLayout):
    
        gridMonthView = ObjectProperty(None)
    
        curMonth = StringProperty()
        curYear = StringProperty()
    
        def __init__(self, **kwargs):
            print ("------- Month View -------")
            self.curMonth = TurnoApp.currCal.getMonth()
            self.curYear = TurnoApp.currCal.getYear()
            super(MonthView, self).__init__(**kwargs)
            self.load_content(True)
            self.printInfo()
    
        def load_content(self, clearWidget = False):
            if (clearWidget):
                print "---- Clear Widgets ----"
                self.gridMonthView.clear_widgets()
    
            lod = list ()
            lod = TurnoApp.currCal.getDaysOfMonth(lod)
            for wid in lod:
                self.gridMonthView.add_widget(DaysInfo(day=wid))
    
        def prevMonth (self):
            print (" ====== click sul mese precedente 1 ======")
            TurnoApp.currCal.setPrevMonth()
            self.curMonth = TurnoApp.currCal.getMonth()
            self.curYear = TurnoApp.currCal.getYear()
            #self.printInfo()
            self.load_content(True)
    
        def nextMonth (self):
            print (" ====== click sul mese successivo ======")
            TurnoApp.currCal.setNextMonth()
            self.curMonth = TurnoApp.currCal.getMonth()
            self.curYear = TurnoApp.currCal.getYear()
            #self.printInfo()
            self.load_content(True)
    
        def printInfo (self):
            print " ____ items ____"
            for key, val in self.ids.items():
                print("key={0}, val={1}".format(key, val))
            print " ____ childs ____"
            for child in self.children:
                print("{} -> {}".format(child, child.id))
            print " ____ walk ____"
            for obj in self.walk():
                print obj
        # end of class: MonthView(GridLayout):
    
    
    class DaysInfo(BoxLayout):
         def __init__(self, **kwargs):
            super(DaysInfo, self).__init__()
            #print ("-- Days Info - {:d} ------".format(kwargs["day"]))
            self.ids["f1"].text =str(kwargs["day"])       
        # end of class: DaysInfo(BoxLayout):
    
    
    class TurnoMainForm(BoxLayout):
    
        def __init__(self, **kwargs):
            super(TurnoMainForm, self).__init__(**kwargs)
            print ("-------TurnoMainForm-------")
            for child in self.children:
                print(child)
        # end of class: TurnoMainForm(BoxLayout):
    
    
    class TurnoApp (App):
        # icon = 'mia_icona.png'
        title = 'Turno Terna'
        currCal = Calendario()
    
        def build (self):
            return TurnoMainForm()
            #return MonthView()
    
        # end of class: TurnoApp (App):
    
    
    TurnoApp().run()
    

I don't try to reduce the code to an example, instead I post everything because it may be better to understand where the problem is and to give me some tip to improve the code.


ANSWERS:


Why does it seem that inside the init method I can't use add_widget for gridMonthView reference/object but it's possible from the other methods of the same class?

Because self.gridMonthView has not yet been set yet (i.e. modified from the default value of None) within the __init__. This is a technical limitation - imagine two widgets with kv rules that reference one another, in this case at least one of them can't reference the other in its __init__ because it will be the first one to actually have been instantiated. This is the kind of effect you see here, the widgets must all be instantiated before references between them can be set.

It works in other methods because you call them only later, after all the instantiation is complete.

You can use something like Clock.schedule_once(self.post_init, 0) and put your widget addition in the post_init method; scheduling with an interval of 0 makes sure it will happen before the next frame, but after everything currently in progress has completed.



 MORE:


 ? Why does this not load the ids of the contents in the BoxLayout? (Kivy)
 ? Kivy: Is there a "BoundedString" property available for TextInputs?
 ? Kivy: Is there a "BoundedString" property available for TextInputs?
 ? Kivy: Is there a "BoundedString" property available for TextInputs?
 ? How to unbind a property automatically binded in Kivy language?
 ? Assign variable value from TextInput in Python (Kivy)
 ? Kivy Framework - Most pythonic solution with TextInput/Label Updating?
 ? Kivy layout positioning problems - not understanding/properly using __init__, kv, kv properties
 ? edit or append text (TextInput class) in kivy
 ? How to set event handler functions in kivy templates?