pythonkivy

Kivy - kv layout doesn't update widgets injected into MainApp


I have a Kivy app where I am injecting a widget as a dependency to the main app. The injected widget has the wrong layout (that was defined in the .kv file). Here are the following files:

Layout is defined using a kv file

# minimal_example/test.kv
<TestAppLayout>
    CustomButton:
        text: 'Custom Button'

<CustomButton>
    text: 'This is a custom button'
    background_color: (0.5, 0.5, 0.5, 1)
    font_size: 25

Custom widgets are defined in a separate file. This is because I wanted to separate out my files.

# minimal_example/custom_widgets.py
from kivy.uix.button import Button


class CustomButton(Button):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

CONSTANT = CustomButton(text='Constant')

When constructing the main app, a CustomButton is injected in as a dependency. But the injected CustomButton has the wrong layout.

# minimal_example/app_with_kv.py

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout

from minimal_example.custom_widgets import CustomButton, CONSTANT


class TestAppLayout(BoxLayout):
    def __init__(self, custom_button: CustomButton, **kwargs):
        super().__init__(**kwargs)
        self.add_widget(CustomButton(text='Added dynamically')) # This has the proper layout
        self.add_widget(custom_button) # This does not

class TestApp(App):
    def __init__(self, custom_button: CustomButton, **kwargs):
        super().__init__(**kwargs)
        self._custom_button = custom_button

    def build(self):
        return TestAppLayout(self._custom_button)



if __name__ == '__main__':
    TestApp(CONSTANT).run()

Exampled kivy app with wrong layout

The injected widget has the wrong layout. What has gone wrong here & how can I achieve the correct layout using this dependency injection pattern?


Solution

  • The problem is that you are building the CONSTANT in custom_widgets.py before the test.kv is loaded. The App class automatically loads a correctly named .kv file (like test.kv in your case), and this is loaded in the build() method. There are several ways to fix this.

    One is to simply remove the CONSTANT definition from custom_widgets.py and add it into the build() method. This requires some restructuring of your code.

    Another way is to force the loading of the test.kv earlier. To do this, you must rename the test.kv file to avoid duplicate loading. For example, rename it to test1.kv, then add:

    Builder.load_file('minimal_example/test1.kv')
    

    just before the import of the custom_widgets.py file.