Search code examples
pythonkivykivy-language

Having issues with Kivy RecycleView data


I am having a strange issue when using Kivy's RecycleView. When trying to pass in a list of dictionaries using a custom widget I made as the viewclass it seems to create the correct number of widgets, however the actual values don't seem to get passed through, resulting in a correct number of "default" widgets. Here is a runnable example I created of the problem I am having:

main.py:

from kivy.app import App
import view


class MainApp(App):
    def build(self):
        return view.PostRV()


if __name__ == '__main__':
    app = MainApp()
    app.run()

main.kv:

<RVContainer>:
    multiselect: True
    touch_multiselect: True
    height: self.minimum_height
    cols: 1
    default_size_hint: 1, None
    default_size: None, dp(110)
    size_hint_y: None

view.py:

from kivy.uix.recycleview import RecycleView
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.gridlayout import GridLayout
from kivy.graphics import Color, RoundedRectangle
from kivy.core.window import Window
from kivy.properties import StringProperty
from kivy.uix.recyclegridlayout import RecycleGridLayout


class PostRV(RecycleView):

    def __init__(self, **var_args):
        super(PostRV, self).__init__(**var_args)

        self.data = [
            {'date': 'a date', 'time': 'a time', 'name': 'a name'},
            {'date': 'another date', 'time': 'another time', 'name': 'another name'}
        ]  # this data does not seem to pass through properly

        self.add_widget(RVContainer())

        self.size_hint = 1, 1
        self.viewclass = Post


class RVContainer(RecycleGridLayout):
    pass
    

# below this line is the custom widget "Post" which I split into multiple classes for easier control and clarity

class Post(AnchorLayout):

    date = StringProperty('')
    time = StringProperty('')
    name = StringProperty('')

    def __init__(self, **var_args):
        super(Post, self).__init__(**var_args)

        self.anchor_y = 'center'
        self.anchor_x = 'center'
        self.size_hint = 1, None
        self.height = '110dp'

        self.add_widget(PostContainer(date=self.date, time=self.time, name=self.name))


class PostContainer(GridLayout):

    date = StringProperty('')
    time = StringProperty('')
    name = StringProperty('')

    def __init__(self, **var_args):
        super(PostContainer, self).__init__(**var_args)

        self.cols = 2
        self.padding = '12dp'
        self.size_hint = (None, None)
        self.width = Window.size[0] / 1.05
        self.height = '100dp'

        self.add_widget(PostShellOne(date=self.date, time=self.time, name=self.name))
        self.add_widget(PostShellTwo())

        self.bind(pos=self.update_rect, size=self.update_rect)

        with self.canvas.before:
            Color(1, 1, 1, .7)
            self.rect = RoundedRectangle(size=self.size, pos=self.pos, radius=[10])

    def update_rect(self, *args):
        self.rect.pos = self.pos
        self.rect.size = self.size
        self.width = Window.size[0] / 1.05


class PostShellOne(GridLayout):

    date = StringProperty('')
    time = StringProperty('')
    name = StringProperty('')

    def __init__(self, **var_args):
        super(PostShellOne, self).__init__(**var_args)

        self.rows = 3

        name_label = Label(text=self.name, color=(0, 0, 0, 1))
        date_label = Label(text=self.date, color=(0, 0, 0, 1))
        time_label = Label(text=self.time, color=(0, 0, 0, 1))

        self.add_widget(name_label)
        self.add_widget(time_label)
        self.add_widget(date_label)


class PostShellTwo(Button):

    def __init__(self, **var_args):
        super(PostShellTwo, self).__init__(**var_args)

        self.text = 'button'
        self.background_color = (0, 0, 0, 0)
        self.background_normal = ''

        self.bind(pos=self.update_rect, size=self.update_rect)

        with self.canvas.before:
            Color(0, 0, 0, .4)
            self.rect = RoundedRectangle(size=self.size, pos=self.pos, radius=[40])

    def update_rect(self, *args):
        self.rect.pos = self.pos
        self.rect.size = self.size

Any help is appreciated, thank you!


Solution

  • The RecycleView works by creating instances of the viewclass (Post in your code), and then assigning the values from the data dictionary. So the __init__() methods of your Post classes create their child classes and pass in the current values of name, date, and time. But those values are still the default values of '' at that time. Then, when the actual values of name, date, and time get set for the Post instance, it is too late, and the values are not passed to the children of the Post instance.

    A way to fix this is to use the kivy language, since it does automatic binding to the StringProperty variables. So, you can expand your main.kv to include the Post class and its children:

    <RVContainer>:
        multiselect: True
        touch_multiselect: True
        height: self.minimum_height
        cols: 1
        default_size_hint: 1, None
        default_size: None, dp(110)
        size_hint_y: None
    <Post>:
        anchor_y: 'center'
        anchor_x: 'center'
        size_hint: 1, None
        height: '110dp'
        PostContainer:
            cols: 2
            padding: '12dp'
            size_hint: (None, None)
            width: app.root.size[0] / 1.05
            height: '100dp'
            canvas:
                Color:
                    rgba: (1, 1, 1, .7)
                RoundedRectangle:
                    size: self.size
                    pos: self.pos
                    radius: [10]
    
            PostShellOne:
                rows: 3
                Label:
                    text: root.name
                    color: 0,0,0,1
                Label:
                    text: root.date
                    color: 0,0,0,1
                Label:
                    text: root.time
                    color: 0,0,0,1
            PostShellTwo:
                text: 'button'
                background_color: (0, 0, 0, 0)
                background_normal: ''
                canvas.before:
                    Color:
                        rgba: (0, 0, 0, .4)
                    RoundedRectangle:
                        size: self.size
                        pos: self.pos
                        radius: [40]
    

    Note that the Labels in PostShellOne use the StringProperty values of the Post class, so they update automatically.

    Then the Post classes can be defined more simply:

    class Post(AnchorLayout):
        date = StringProperty('')
        time = StringProperty('')
        name = StringProperty('')
    
    
    class PostContainer(GridLayout):
        pass
    
    
    class PostShellOne(GridLayout):
        pass
    
    
    class PostShellTwo(Button):
        pass