kivymd / KivyMD

KivyMD is a collection of Material Design compliant widgets for use with Kivy, a framework for cross-platform, touch-enabled graphical applications. https://youtube.com/c/KivyMD https://twitter.com/KivyMD https://habr.com/ru/users/kivymd https://stackoverflow.com/tags/kivymd
https://kivymd.readthedocs.io
MIT License
2.21k stars 664 forks source link

Enhancement request: MDTabs 2.0.1 - added 2 examples #1658

Open RobertFlatt opened 6 months ago

RobertFlatt commented 6 months ago

Description of the Feature

I wish to do lazy loading of MDTabsPrimary tabs and tab content with KivyMD 2.X , because the data displayed comes from some server. Of course I assume I must statically define at least one tab and its contents.

I would like 1) a method to append a new tab (and it's contents) at any time step. 2) a method to replace the content of any existing tab at any time step.

I was able implement lazy loading of tabs with KivyMD 1.X

Thanks for your time, I think version 2 is great.

HeaTTheatR commented 6 months ago

Thank you. Do you have any problems implementing this or can we close this issue?

RobertFlatt commented 6 months ago

I can add tabs (but not their content), and I have a problem adding or changing Tab content. I think the issue is a different time step to that in which the MDTabsPrimary was instantiated.

I expect I can workaround by rebuilding the MDTabsPrimary as each tab contents arrives, but there will a (triangular) performance hit due to re-building tab contents on each new tab content. 8 tabs would be a total of 36 tab contents builds.

So I'd like an enhancement, the importance of dynamic loading of hidden tab contents in the context of your project as a whole is yours to prioritize.

Thanks for your time.

RobertFlatt commented 6 months ago

@HeaTTheatR

I tried my assumed workaround, dynamically rebuilding (in Python) both MDTabsPrimary, and also just its children. I ran into problems with tab event scheduling:

   File "C:\Users\Bobf\AppData\Local\Programs\Python\Python311\Lib\site-packages\kivymd\uix\tab\tab.py", line 1241, in <lambda>
     lambda x: self._tabs_carousel.current_slide.tab_item.dispatch(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 AttributeError: 'MDLabel' object has no attribute 'tab_item' 

This MDLabel instance is the replaced tab content.

For the moment I'm out of ideas, so I would like to leave the issue open.

HeaTTheatR commented 6 months ago

Okay, thanks for the analysis. I'll check it out...

RobertFlatt commented 6 months ago

@HeaTTheatR

Two Examples of changing Content at a later time step. The first example is appending the second example is replacing

1) Adding more tabs and content at a later time step. The tabs are added but the new content is the content of the previous last tab.

from kivy.lang import Builder
from kivy.clock import Clock

from kivymd.app import MDApp
from kivymd.uix.label import MDLabel
from kivymd.uix.tab import (
    MDTabsItemIcon,
    MDTabsItemText,
    MDTabsItem,
)

KV = '''
MDScreen:
    md_bg_color: self.theme_cls.backgroundColor

    MDTabsPrimary:
        id: tabs
        pos_hint: {"center_x": .5, "center_y": .5}
        size_hint_x: .6

        MDDivider:

        MDTabsCarousel:
            id: related_content_container
            size_hint_y: None
            height: dp(320)
'''

class Example(MDApp):
    def on_start(self):
        super().on_start()
        self.add_tabs(["Flights","Trips","Explore"])
        Clock.schedule_once(self.more_tabs, 2)

    def more_tabs(self,dt):
        #### These tabs are added, but not the contents
        self.add_tabs(["Dogs","Cats","Snakes"])

    def add_tabs(self, names):
        for tab_name in names:
            self.root.ids.tabs.add_widget(
                MDTabsItem(
                    MDTabsItemText(
                        text=tab_name,
                    ),
                )
            )
            self.root.ids.related_content_container.add_widget(
                MDLabel(
                    text=tab_name,
                    halign="center",
                )
            )
            self.root.ids.tabs.switch_tab(icon="airplane")

    def build(self):
        self.theme_cls.primary_palette = "Olive"
        return Builder.load_string(KV)

Example().run()

===================================================================== 2) Replacing the contents of existing tabs at a later time step. Works, but crashes on subsequent tab change initiated by clicking tab:

   File "C:\Users\Bobf\AppData\Local\Programs\Python\Python311\Lib\site-packages\kivy\uix\carousel.py", line 295, in load_slide
     start, stop = slides.index(self.current_slide), slides.index(slide)

Or with swipe:

   File "C:\Users\Bobf\AppData\Local\Programs\Python\Python311\Lib\site-packages\kivymd\uix\tab\tab.py", line 1034, in android_animation
     a = instance.current_slide.tab_item
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 AttributeError: 'MDLabel' object has no attribute 'tab_item'
from kivy.lang import Builder
from kivy.clock import Clock

from kivymd.app import MDApp
from kivymd.uix.label import MDLabel
from kivymd.uix.tab import (
    MDTabsItemIcon,
    MDTabsItemText,
    MDTabsItem,
)

KV = '''
MDScreen:
    md_bg_color: self.theme_cls.backgroundColor

    MDTabsPrimary:
        id: tabs
        pos_hint: {"center_x": .5, "center_y": .5}
        size_hint_x: .6

        MDDivider:

        MDTabsCarousel:
            id: related_content_container
            size_hint_y: None
            height: dp(320)
'''

class Example(MDApp):
    def on_start(self):
        super().on_start()
        self.add_tabs(["Flights","Trips","Explore"])
        # Works, but crashes on tab change.
        Clock.schedule_once(self.replace_tab_content, 2)

    def replace_tab_content(self,dt):
        new_content = ["peace","love","understanding"]
        container = self.root.ids.related_content_container
        container.clear_widgets()
        for content in new_content:
            container.add_widget(
                MDLabel(
                    text=content,
                    halign="center",
                )
            )

    def add_tabs(self, names):
        for tab_name in names:
            self.root.ids.tabs.add_widget(
                MDTabsItem(
                    MDTabsItemText(
                        text=tab_name,
                    ),
                )
            )
            self.root.ids.related_content_container.add_widget(
                MDLabel(
                    text=tab_name,
                    halign="center",
                )
            )
            self.root.ids.tabs.switch_tab(icon="airplane")

    def build(self):
        self.theme_cls.primary_palette = "Olive"
        return Builder.load_string(KV)

Example().run()