user2915097
user2915097

Reputation: 32166

kivy crash if widget used in kv file, succeeds if used in Python file

I have encountered the following problem:

The example (mainok.py, testok.kv) successfully starts,

$ cat mainok.py
from kivy.app import App
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
class BuddyList(BoxLayout):
    list_view = ObjectProperty()
    def __init__(self, **kwargs):
        super(BuddyList, self).__init__(**kwargs)
        assert(self.list_view is not None)
        self.list_view.adapter.data = ['v1', 'v2']
    def roster_converter(self, index, txt):
        result = {
            "text": "%s %d" % (txt, index),
            'size_hint_y': None,
            'height': 25
        }
        return result
class TestForm(BoxLayout):
    text_input = ObjectProperty()

    def __init__(self, **kwargs):
        super(TestForm, self).__init__(**kwargs)
        self.buddy_list = BuddyList()
        self.add_widget(self.buddy_list)
class TestokApp(App):
    pass
if __name__ == '__main__':
    TestokApp().run()

$ cat testok.kv
#:import la kivy.adapters.listadapter
#:import ku kivy.uix.listview
TestForm:
<BuddyList>:
   list_view: list_view
    ListView:
        id: list_view
        adapter:
            la.ListAdapter(data=[], cls=ku.ListItemButton,
            args_converter=root.roster_converter)
<TestForm>:
    orientation: 'vertical'
    BoxLayout:
        Label:
            text: 'the label'

The example (mainko.py, testko.kv) crashes:

$ cat mainko.py
from kivy.app import App
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
class BuddyList(BoxLayout):
    list_view = ObjectProperty()
    def __init__(self, **kwargs):
        super(BuddyList, self).__init__(**kwargs)
        assert(self.list_view is not None)
        self.list_view.adapter.data = ['v1', 'v2']
    def roster_converter(self, index, txt):
        result = {
            "text": "%s %d" % (txt, index),
            'size_hint_y': None,
            'height': 25
        }
        return result
class TestForm(BoxLayout):
    text_input = ObjectProperty()
    def __init__(self, **kwargs):
        super(TestForm, self).__init__(**kwargs)
class TestkoApp(App):
    pass
if __name__ == '__main__':
    TestkoApp().run()

$ cat testko.kv
#:import la kivy.adapters.listadapter
#:import ku kivy.uix.listview
TestForm:
<BuddyList>:
    list_view: list_view
    ListView:
        id: list_view
        adapter:
            la.ListAdapter(data=[], cls=ku.ListItemButton,
            args_converter=root.roster_converter)
<TestForm>:
    orientation: 'vertical'
    BoxLayout:
        Label:
            text: 'the label'
    BuddyList:

Crash with the following error

  assert(self.list_view is not None)
     AssertionError

Differences between the 2 are:

$ diff -u mainko.py ../ok/mainok.py
--- mainko.py       2013-10-23 14:11:26.976723764 +0200
+++ ../ok/mainok.py 2013-10-23 14:12:34.976841090 +0200
@@ -26,10 +26,13 @@

 def __init__(self, **kwargs):
     super(TestForm, self).__init__(**kwargs)
+        self.buddy_list = BuddyList()
+        self.add_widget(self.buddy_list)

-class TestkoApp(App):
+
+class TestokApp(App):
     pass

 if __name__ == '__main__':
-    TestkoApp().run()
+    TestokApp().run()

$ diff -u testko.kv ../ok/testok.kv
--- testko.kv       2013-10-23 14:10:11.948299722 +0200
+++ ../ok/testok.kv 2013-10-23 14:16:51.352688453 +0200
@@ -16,5 +16,4 @@
 BoxLayout:
     Label:
         text: 'the label'
-    BuddyList:

Any idea why the first one succeeds and the second fails?

Thanks,

Upvotes: 1

Views: 617

Answers (1)

inclement
inclement

Reputation: 29450

It looks like the problem is that the __init__ method doesn't actually fully parse and setup the kv instructions, though I'm not sure of the details - I guess it leaves some stuff scheduled in the eventloop. When you try to check self.list_view, you get None because this hasn't been set yet.

If you use the clock, you can arrange to carry out your post_init stuff after everything else scheduled so far (and so after the kv instructions have been fully carried out) but before the next frame. Here's a modification to your mainko.py doing such a thing. It seems to work for me.

from kivy.app import App 
from kivy.properties import ObjectProperty 
from kivy.uix.boxlayout import BoxLayout 
from kivy.clock import Clock 

class BuddyList(BoxLayout): 
    list_view = ObjectProperty() 

    def __init__(self, **kwargs): 
        super(BuddyList, self).__init__(**kwargs) 
        Clock.schedule_once(self.post_init, 0) 

    def post_init(self, *args): 
        assert self.list_view is not None 
        self.list_view.adapter.data = ['v1', 'v2'] 

    def roster_converter(self, index, txt): 
        result = { 
            "text": "%s %d" % (txt, index), 
            'size_hint_y': None, 
            'height': 25 
        } 
        return result 

class TestForm(BoxLayout): 
    text_input = ObjectProperty() 
    def __init__(self, **kwargs): 
        super(TestForm, self).__init__(**kwargs) 


class TestkoApp(App): 
    pass 


if __name__ == '__main__': 
    TestkoApp().run() 

Upvotes: 2

Related Questions