Search code examples
pythonlistviewsortinglistadapterkivy

Kivy: Having trouble getting ListView to update when sorting ListAdapter data


I'm trying to sort a list of dictionaries in a listview. I'm going about it by changing the ListAdapter.data.

In the kv file a spinner button above the listview selects the sort value and on_release makes a call to root.beer_sort which changes the ListAdapter (named beer_la) data. Printing out the beer_la.data to the console, it looks like it changes, but the App's display order doesn't update.

class CellarDoor(BoxLayout):
    def __init__(self, **kwargs):
        self.beer_archive = []
        self.wine_archive = []
        with open(join(FILE_ROOT, 'beer_archive.csv'), 'rb', 1) as beer_csv:
            self.beer_archive = list(csv.DictReader(beer_csv))

        self.beer_la = ListAdapter(data=self.beer_archive, 
                          args_converter=self.beer_formatter,
                          cls=CompositeListItem,         
                          selection_mode='single',
                          allow_empty_selection=True)
        super(CellarDoor, self).__init__(**kwargs)

    def beer_formatter(self, row_index, beer_data):
        return {'text': beer_data['Beer'],
                'size_hint_y': None,
                'height': 50,
                'cls_dicts': [{'cls': ListItemImage,
                               'kwargs': {'size_hint_x': .2,
                                          'size': self.parent.size,
                                          'cellar': 'Beer',
                                          'style': beer_data['Style']}},
                              {'cls': ListItemLabel,
                               'kwargs': {'text': beer_data['Beer']}},
                              {'cls': ListItemButton,
                               'kwargs': {'text': str(beer_data['Stock'])}}]}

    def beer_sort(self, sort, reverse):
        self.beer_la.data = sorted(self.beer_archive,
                                   key=lambda k: k[sort],
                                   reverse=reverse)
        print "new sort = %s" % sort
        print self.beer_la.data

The relevant portion of the kv file

<CellarDoor>
    ...
    Spinner:
        id: beer_sort_spinner
        text: 'Brewery'
        values: ['Brewery', 'Beer', 'Year', 'Style', 'Stock', 'Rating', 'Price']
        on_release: root.beer_sort(self.text, (True if future_search_button.state=='down' else False))
    ToggleButton:
        id: future_search_button
        text: 'Reverse'
    Button:
        text: 'Add Beer'
    ListView:
        adapter: root.beer_la

Solution

  • I hope I can help a little bit with your problem. I found out that if I add the list view after calling the constructor of the CellarDoor (instead of using Kivy Language) then the sort method start working. My example is a simplified version of yours but the behaviour that you describe was still present.

    If you uncomment:

    #    ListView:
    #        adapter: root.beer_la
    

    and comment:

            self.add_widget(ListView(adapter=self.beer_la))
    

    then, the sorting stop working just as you describe.

    Curiously, if I have a big list (let's say 100), the ListView is updated after performing and action on it (scrolling or selecting) and item. Somehow this means that the property of the adapter is not bound to update the screen. This could be an issue that might deserve to be reported to the developers in Github. I cannot explain you better but I hope at least it solves your problem. You might also want to try create and add the adapter directly in Kivy Language.

    from kivy.adapters.listadapter import ListAdapter
    from kivy.uix.listview import ListView, ListItemButton
    from kivy.uix.boxlayout import BoxLayout
    from kivy.lang import Builder
    from kivy.app import App
    
    Builder.load_string("""
    <CellarDoor>:
        ToggleButton:
            text: 'Reverse'
            on_state: root.beer_sort((True if self.state=='down' else False))
    #    ListView:
    #        adapter: root.beer_la
    """)
    
    class CellarDoor(BoxLayout):
        def __init__(self, **kwargs):
            self.beer_archive = ["Item #{0}".format(i) for i in range(10)]
            self.beer_la = ListAdapter(data=self.beer_archive, 
                              cls=ListItemButton,
                              selection_mode='single',
                              allow_empty_selection=True)
    
            super(CellarDoor, self).__init__(**kwargs)
            self.add_widget(ListView(adapter=self.beer_la))
    
    
        def beer_sort(self, reverse):
            self.beer_la.data = sorted(self.beer_archive,
                                       reverse=reverse)
            print self.beer_la.data
    
    class TestApp(App):
        def build(self):
            return CellarDoor()
    
    if __name__ == '__main__':
        TestApp().run()