Kivy ResponsiveView and widget notifications, how to keep all views synched?

Question:

Trying to implement an application using ResponsiveView and having trouble both linking all the widgets and keeping all three view consistent. I began with a prototype using the backdrop view, which was fine for a mobile device, but fell short for a tablet or desktop. In the initial prototype I was able to identify the widget hierarchy of the status bar(s) and simply call them from a bound application level method. That is obviously not going to work in the ResponseView implementation, as each view may have different instances of the widgets. I expect that the best solution may be to create my own custom events, but verification or the suggestion of another better approach would be appreciated.

I would like to be able to update the right status bar to display properties about the selected items in list one. Since each of the three views may have its own instance of the status bar and lists, I need to update each for every event. Or figure out how to have a single widget for each item that can be used in each view. The implementation below will work for the tablet view, but not the mobile or desktop views. The mobile view is incomplete, as the full backdrop implementation is not present.

enter image description here

Below is a rather lengthy prototype that should display the problem(s):

from kivymd.app import MDApp
from kivymd.uix.list import MDList
from kivymd.uix.scrollview import ScrollView
from kivymd.uix.list import OneLineIconListItem
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.screen import MDScreen

from kivymd.uix.responsivelayout import MDResponsiveLayout
from kivymd.uix.toolbar import MDTopAppBar
from kivy.properties import StringProperty, BooleanProperty, ObjectProperty

Version = '0.1'


MainAppKV = '''
#:import NoTransition kivy.uix.screenmanager.NoTransition
#:import Window kivy.core.window.Window
#:import IconLeftWidget kivymd.uix.list.IconLeftWidget

<NotificationBar>:
    id: top_bar
    title: "Title"
    anchor_title: "center"

<LeftStatusBar>:
    id: left_status_bar
    IconLeftWidget:
        id: status_icon
        icon: ''

<RightStatusBar>:
    id: right_status_bar
    IconLeftWidget:
        id: status_icon
        icon: ''

<TableOneHeader>:
    id: "table_one_header" 
    size_hint: [1, .12]
    orientation: "horizontal"

    Button:
        id: "column_1"
        text: "Column 1"
        on_press: root.button_press(self)
    Button:
        id: "column_2"
        text: "Column 2"
    Button:
        id: "column_3"
        text: "Column 3"

<TableTwoHeader>:
    id: "table_two_header" 
    size_hint: [1, .12]
    orientation: "horizontal"

    Button:
        id: "column_1"
        text: "Column 1"
    Button:
        id: "column_2"
        text: "Column 2"

<ScrollViewOne>:
    id: "scrollview_one"

    MDSelectionList:
        id: "mdselection_list"
        spacing: "12dp"
        on_selected: app.on_selected(*args)
        on_unselected: app.on_unselected(*args)

        OneLineIconListItem:
            text: "List One, Item One"

        OneLineIconListItem:
            text: "List One, Item Two"

<ScrollViewTwo>:
    id: "scrollview_two"
    ListTwo:
        id: "list_two"

        OneLineIconListItem:
            text: "List Two, Item One"

        OneLineIconListItem:
            text: "List Two, Item Two"

<ListOneLayout>:
    id: "list_one_layout"
    orientation: "vertical"

    TableOneHeader:
    
    ScrollViewOne:

<ListTwoLayout>:
    id: "list_two_layout"
    orientation: "vertical"

    TableTwoHeader:
    
    ScrollViewTwo:

<BackDropLayout>:
    id: "backdrop_layout"
    orientation: "vertical"

    NotificationBar:

    BoxLayout:
        orientation: "horizontal"
        size_hint: (1, .1)
        
        LeftStatusBar:
            size_hint: (.6, 1)

        RightStatusBar:
            size_hint: (.4, 1)

    ListOneLayout:

<MyBackdropBackLayer@ScreenManager>
    transition: NoTransition()

    MDScreen:
        ListOneLayout:

<SideBySideLayout>
    orientation: "vertical"
    NotificationBar:

    BoxLayout:
        orientation: "horizontal"

        BoxLayout:
            orientation: "vertical"
            size_hint: (.5, 1)

            LeftStatusBar:

            ListTwoLayout:

        BoxLayout:
            orientation: "vertical"
            size_hint: (.5, 1)
            RightStatusBar:

            ListOneLayout:

<MobileView>
    id: "mobile_view"
    name: "mobile"
    MDScreen:
        name: "backdrop"

        BackDropLayout:
            id: "mobile_layout"

<TabletView>
    id: "tablet_view"
    name: "tablet"
    SideBySideLayout:
        id: "tablet_layout"

<DesktopView>
    id: "desktop_view"
    name: "desktop"
    SideBySideLayout:
        id: "desktop_layout"

ResponsiveView:
'''

class ScrollViewOne(ScrollView):
    pass

class TableOneHeader(MDBoxLayout):
    pass

class TableTwoHeader(MDBoxLayout):
    pass

class BackDropLayout(MDBoxLayout):
    pass


class SideBySideLayout(MDBoxLayout):
    pass


class MobileView(MDScreen):
    pass


class TabletView(MDScreen):
    pass


class DesktopView(MDScreen):
    pass


class ResponsiveView(MDResponsiveLayout, MDScreen):
    def __init__(self, **kw):
        super().__init__(**kw)
        self.mobile_view = MobileView()
        self.tablet_view = TabletView()
        self.desktop_view = DesktopView()


class LeftStatusBar(OneLineIconListItem):
    status_text = StringProperty("Left-Status")

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.text = self.status_text


class RightStatusBar(OneLineIconListItem):
    in_selection = BooleanProperty(False)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.text = "None selected"

    def update(self, item, remove=False):
        if remove:
            self.text = 'None selected'
            return
        self.text = "update selection"


class NotificationBar(MDTopAppBar):
    pass


class ListOneLayout(MDBoxLayout):
    pass


class ListTwoLayout(MDBoxLayout):
    pass


class ScrollViewTwo(ScrollView):
    pass


class ListTwo(MDList):
    pass


class MainApp(MDApp):
    status_left = ObjectProperty()
    status_right = ObjectProperty()

    def __init__(self):
        super().__init__()
        self.title = "Title (v{})".format(Version)

    def build(self):
        self.root = Builder.load_string(MainAppKV)
        self.status_right = self.root.tablet_view.children[0].children[0].children[0].children[1]
        self.status_left = self.root.tablet_view.children[0].children[0].children[1].children[1]
        return self.root

    def on_selected(self, instance_selection_list, instance_selection_item):
        print("on_selected {}".format(instance_selection_item))
        self.status_right.update(0)

    def on_unselected(self, instance_selection_list, instance_selection_item):
        print("on_unselected {}".format(instance_selection_item))
        self.status_right.update(0, True)


if __name__ == "__main__":
    MainApp().run()
Asked By: Bill Packard

||

Answers:

If you define a Property in your App class, like this:

class MainApp(MDApp):
    left_status_text = StringProperty('Left-Status from App')

Then, you can use that same property in every instance of LeftStatusBar by using it in the kv:

<LeftStatusBar>:
    id: left_status_bar
    text: app.left_status_text  # refers to Property in App
    IconLeftWidget:
        id: status_icon
        icon: ''
Answered By: John Anderson

Looking further into the ResponseView, I found that it isn’t necessarily intended to support synchronizing the distinct views, but rather allowing a single app to display one of 3 views. The other problem that I had was to update the subordinate widgets appropriate for the selected view. To accomplish this, I added Object properties to the ResponsiveView and set them in the on_change_screen_type().

class ResponsiveView(MDResponsiveLayout, MDScreen):
active_view = ObjectProperty()
status_left = ObjectProperty()
status_right = ObjectProperty()

def on_change_screen_type(self, *args):
    # first child is the Active screen
    self.active_view = self.children[0]
    self.status_left = self.active_view.ids.view_layout.ids.left_status
    self.status_right = self.active_view.ids.view_layout.ids.right_status
Answered By: Bill Packard
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.