Search code examples
pythonkivykivymd

How to update data in nested RecycleViews?


I've got a follow-up question on Is it possible to change font size in Recycleview widgets? (thank you John for your help over there).

I'm having a bunch of nested MDRecycleView instances and I'd like to update their data, specifically font_size. Updating font_size in my original question turned out not to be so complicated because you have a reference to the actual MDRecycleView to call the refresh_from_data() method from. Now with the nested MDRecycleView instances all defined inside the .kv file, I'm still able to "update" the data of all the instances but haven't found a way to obtain the references of nested MDRecycleView instances to call their respected refresh_from_data() method. I would also like to keep the current architecture where I have all the objects living inside a .kv file (string).

from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager
from kivy.uix.screenmanager import Screen
from kivymd.uix.boxlayout import MDBoxLayout

kv = """
<Content>:
    bg_color: app.theme_cls.primary_dark
    item1: ''
    item2: ''
    font_size: '15dp'

    MDGridLayout:
        rows: 2
        MDLabel:
            id: firstLabelId
            halign: 'center'
            text: root.item1
            font_size: root.font_size

        MDLabel:
            id: secondLabelId
            halign: 'center'
            md_bg_color: root.bg_color
            text: root.item2
            font_size: root.font_size

<DailyService>:
    bg_color: app.theme_cls.primary_dark
    day: ''
    innerData: []
    font_size: '15dp'

    MDGridLayout:
        rows: 2
        MDLabel:
            id: serviceId
            halign: 'center'
            text: root.day
            font_size: root.font_size

        MDRecycleView:
            viewclass: 'Content'
            id: statisticContentRecycleViewId
            data: root.innerData
            do_scroll_y: False
            do_scroll_x: False
                            
            RecycleBoxLayout:
                default_size_hint: 1, 1
                orientation: 'vertical'
    
<MainScreen>:
    name: 'mainScreen'
    rvid: myRv

    MDRelativeLayout:
        orientation: 'vertical'

        MDRecycleView:
            viewclass: 'DailyService'
            id: myRv
            RecycleBoxLayout:
                default_size: None, dp(200)
                default_size_hint: 1, None
                size_hint_y: None
                height: self.minimum_height
                orientation: 'vertical'

        MDSlider:
            color: 'white'
            orientation: 'horizontal'
            size_hint: (0.2, 0.2)
            pos_hint: {"x":0.4, "top": 1}
            min: 10
            value: 20
            max: 30
            on_value_normalized: root.fontSizeSlider(self.value)


MyScreenManager:
    mainScreen: mainScreenId
        
    MainScreen:
        id: mainScreenId
"""

class Content(MDBoxLayout):
    pass

class DailyService(MDBoxLayout):
    pass

class MainScreen(Screen):
    def __init__(self, **kwargs):
        super(MainScreen, self).__init__(**kwargs)

    def fontSizeSlider(self, value):
        rv = self.ids.myRv
        data = rv.data
        for v in data:
            v['font_size'] = str(int(value)) + 'dp'
            '''
            innerData = v['innerData']
            for innerV in innerData:
                innerV['font_size'] = str(int(value)) + 'dp'
            ### I believe the missing refresh_from_data() calls cause the issue
            '''

        rv.refresh_from_data()

    
class MyScreenManager(ScreenManager):    
    def __init__(self, **kwargs):
        super(MyScreenManager, self).__init__(**kwargs)


class MyApp(MDApp):
    def on_start(self):
        data = []
        for i in range(10):
            innerData = []
            for i in range(2):
                innerData.append({'item1': 'ITEM1',
                                  'item2': 'ITEM2'})
            data.append({'day': 'DAY','innerData': innerData})
        self.root.ids.mainScreenId.rvid.data = data

    def build(self):
        self.theme_cls.theme_style = 'Dark'
        self.theme_cls.primary_palette = 'Blue'
        self.theme_cls.accent_palette = 'Amber'
        return Builder.load_string(kv)

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



Solution

  • The refresh_from_data() method of a RecycleView can be called directly, or it will be triggered when the data of a RecyclView is changed. Unfortunately, changing an individual item in one of the dicts in the data list is not enough to trigger the update. So, you can trigger the refresh_from_data() method of the nested RecycleView by forcing a change in its data.

    def fontSizeSlider(self, value):
        rv = self.ids.myRv
        data = rv.data
        for v in data:
            v['font_size'] = str(int(value)) + 'dp'
            innerData = v['innerData']
            new_inner = copy.deepcopy(innerData)
            for innerV in new_inner:
                innerV['font_size'] = str(int(value)) + 'dp'
            v['innerData'] = new_inner  # this will trigger a call to refresh_from_data on the nested RecycleView
        rv.refresh_from_data()