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 668 forks source link

Issues with reload MDHero #1514

Open mrTokar opened 1 year ago

mrTokar commented 1 year ago

Description of the Bug

I tried to add an action to a hero in my application and faced this error. Below is a model of the situation in which my problem occurs. This code works great. But if I clear and then refill the GridLayout on screen A (these actions in the example can be called using the corresponding buttons), all the animation associated with the movement of the Heroes breaks. Also, if we add a new Hero and click on it, screen B will not display a picture, and returning to screen A will raise a KeyError exception.

If you look at the children of screen A while the application is running, you can find an MDHeroFrom with the corresponding new Hero tag. Why, when the application is running, the framework cannot find the required MdHeroFrom.

Python Code

from kivymd.uix.hero import MDHeroFrom
from kivymd.uix.screen import MDScreen
from kivymd.uix.screenmanager import MDScreenManager
from kivymd.app import MDApp

from kivy.animation import Animation
from kivy.clock import Clock
from kivy.properties import ObjectProperty, DictProperty

hero_data = [f'obj{i}' for i in range(1, 4)]

class Card(MDHeroFrom):

    # kwargs
    scr_manager = ObjectProperty()

    def __init__(self, name: str, **kwargs):
        super().__init__(**kwargs)
        self.tag = name

    def on_release(self):
        """Switch screen and post data"""
        scr_b = self.scr_manager.scr_b
        scr_b.set_atr_obj(self.tag, self)

        def switch_screen(*args):
            self.scr_manager.current_heroes = [self.tag]
            scr_b.hero_to.tag = self.tag
            self.scr_manager.current = "scr_b"

        Clock.schedule_once(switch_screen, 0.2)

class ScreenB(MDScreen):
    name = "scr_b"

    def set_atr_obj(self, id_obj, hero):
        """Set attribute with id opening obj and remeber hero.
        :param:`id_obj` shared object's id
        :param:`hero` object of instance `MDHero`"""

        self.id_obj = id_obj
        self.hero_from = hero

    def go_back(self):
        """Returns to the page with the list of houses"""
        self.manager.current_heroes = [self.hero_to.tag]
        self.manager.current = "scr_a"

class ScreenA(MDScreen):
    box = ObjectProperty()
    name = 'scr_a'

class Container(MDScreenManager):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.scr_a = ScreenA()
        self.scr_b = ScreenB()
        self.add_widget(self.scr_a)
        self.add_widget(self.scr_b)

class HeroApp(MDApp):

    def __init__(self, **kwargs):

        self.data = {
            "Add new": [
                'plus',
                'on_release', self.add
            ],
            "Clear": [
                'delete-outline',
                'on_release', self.clear
            ],
            "Show": [
                'eye-outline',
                'on_release', self.show
            ]
        }

        super().__init__(**kwargs)

    def build(self):
        self.theme_cls.theme_style = "Dark"
        self.container = Container()
        return self.container

    def on_start(self):
        self.show()

    def add(self, btn=None):
        hero_data.append('New obj')
        self.clear()
        self.show()

    def clear(self, btn=None):
        self.container.scr_a.box.clear_widgets()

    def show(self, btn=None):
        box = self.container.scr_a.box
        for hero_name in hero_data:
            box.add_widget(Card(hero_name, scr_manager= self.container))

if __name__ == "__main__":
    HeroApp().run()

KV file

<Card>
    padding: 4
    size_hint: 0.5, None
    size_y: dp(200)
    radius: 24

    MDSmartTile:
        radius: 24
        box_radius: 0, 0, 24, 24
        box_color: 0, 0, 0, .5
        source: "testdata/house.jpg"
        size_hint: None, None
        size: root.size
        on_release: root.on_release()

        OneLineListItem:
            text: root.tag

<ScreenB>

    heroes_to: [hero_to]
    hero_to: hero_to

    MDBoxLayout:
        orientation: "vertical"

        MDHeroTo:
            id: hero_to
            pos_hint: {"top": 1}

        MDLabel:
            text: 'Someone widgets'

    MDIconButton:
        icon: "arrow-left"
        pos_hint: {"top": 1, "right": .12}
        on_release: root.go_back()

<ScreenA>
    box: box

    MDGridLayout:
        id: box
        cols: 2

    MDFloatingActionButtonSpeedDial:
        data: app.data

Log

[INFO] [Logger      ] Record log in C:\Users\Александр\.kivy\logs\kivy_23-06-20_45.txt
[INFO] [deps        ] Successfully imported "kivy_deps.gstreamer" 0.3.3
[INFO] [deps        ] Successfully imported "kivy_deps.angle" 0.3.2
[INFO] [deps        ] Successfully imported "kivy_deps.glew" 0.3.1
[INFO] [deps        ] Successfully imported "kivy_deps.sdl2" 0.4.5
[INFO] [Kivy        ] v2.1.0
[INFO] [Kivy        ] Installed at "C:\Users\Александр\AppData\Local\Programs\Python\Python38-32\lib\site-packages\kivy\__init__.py"
[INFO] [Python      ] v3.8.10 (tags/v3.8.10:3d8993a, May  3 2021, 11:34:34) [MSC v.1928 32 bit (Intel)]
[INFO] [Python      ] Interpreter at "C:\Users\Александр\AppData\Local\Programs\Python\Python38-32\python.exe"
[INFO] [Logger      ] Purge log fired. Processing...
[INFO] [Logger      ] Purge finished!
[INFO] [KivyMD      ] 1.1.1, git-Unknown, 2023-02-23 (installed at "C:\Users\Александр\AppData\Local\Programs\Python\Python38-32\lib\site-packages\kivymd\__init__.py")
[INFO] [Factory     ] 189 symbols loaded
[INFO] [Image       ] Providers: img_tex, img_dds, img_sdl2, img_pil (img_ffpyplayer ignored)
[INFO] [Text        ] Provider: sdl2
[INFO] [Window      ] Provider: sdl2
[INFO] [GL          ] Using the "OpenGL" graphics system
[INFO] [GL          ] GLEW initialization succeeded
[INFO] [GL          ] Backend used <glew>
[INFO] [GL          ] OpenGL version <b'3.3.0'>
[INFO] [GL          ] OpenGL vendor <b'NVIDIA Corporation'>
[INFO] [GL          ] OpenGL renderer <b'GeForce 9600 GT/PCIe/SSE2'>
[INFO] [GL          ] OpenGL parsed version: 3, 3
[INFO] [GL          ] Shading version <b'3.30 NVIDIA via Cg compiler'>
[INFO] [GL          ] Texture max size <8192>
[INFO] [GL          ] Texture max units <32>
[INFO] [Window      ] auto add sdl2 input provider
[INFO] [Window      ] virtual keyboard not allowed, single mode, not docked
[WARNING] Deprecated property "<ObjectProperty name=hero_to>" of object "<Screen name='scr_b'>" was accessed, it will be removed in a future version
[INFO] [Base        ] Start application main loop
[INFO] [GL          ] NPOT texture support is available
[INFO] [Loader      ] using a thread pool of 2 workers
[INFO] [GL          ] Unpack subimage support is available
[INFO] [Base        ] Leaving application in progress...
 Traceback (most recent call last):
   File "testdata\hero.py", line 109, in <module>
     HeroApp().run()
   File "C:\Users\Александр\AppData\Local\Programs\Python\Python38-32\lib\site-packages\kivy\app.py", line 955, in run
     runTouchApp()
   File "C:\Users\Александр\AppData\Local\Programs\Python\Python38-32\lib\site-packages\kivy\base.py", line 574, in runTouchApp
     EventLoop.mainloop()
   File "C:\Users\Александр\AppData\Local\Programs\Python\Python38-32\lib\site-packages\kivy\base.py", line 339, in mainloop
     self.idle()
   File "C:\Users\Александр\AppData\Local\Programs\Python\Python38-32\lib\site-packages\kivy\base.py", line 383, in idle
     self.dispatch_input()
   File "C:\Users\Александр\AppData\Local\Programs\Python\Python38-32\lib\site-packages\kivy\base.py", line 334, in dispatch_input
     post_dispatch_input(*pop(0))
   File "C:\Users\Александр\AppData\Local\Programs\Python\Python38-32\lib\site-packages\kivy\base.py", line 302, in post_dispatch_input
     wid.dispatch('on_touch_up', me)
   File "kivy\_event.pyx", line 731, in kivy._event.EventDispatcher.dispatch
   File "C:\Users\Александр\AppData\Local\Programs\Python\Python38-32\lib\site-packages\kivymd\uix\button\button.py", line 1187, in on_touch_up
     return super().on_touch_up(touch)
   File "C:\Users\Александр\AppData\Local\Programs\Python\Python38-32\lib\site-packages\kivymd\uix\behaviors\ripple_behavior.py", line 404, in on_touch_up
     return super().on_touch_up(touch)
   File "C:\Users\Александр\AppData\Local\Programs\Python\Python38-32\lib\site-packages\kivy\uix\behaviors\button.py", line 179, in on_touch_up
     self.dispatch('on_release')
   File "kivy\_event.pyx", line 727, in kivy._event.EventDispatcher.dispatch
   File "kivy\_event.pyx", line 1307, in kivy._event.EventObservers.dispatch
   File "kivy\_event.pyx", line 1191, in kivy._event.EventObservers._dispatch
   File "C:\Users\Александр\AppData\Local\Programs\Python\Python38-32\lib\site-packages\kivy\lang\builder.py", line 55, in custom_callback
     exec(__kvlang__.co_value, idmap)
   File "D:\A. Programming\A.Python\TodaCheck\testdata\hero.kv", line 38, in <module>
     on_release: root.go_back()
   File "testdata\hero.py", line 48, in go_back
     self.manager.current = "scr_a"
   File "kivy\properties.pyx", line 520, in kivy.properties.Property.__set__
   File "kivy\properties.pyx", line 567, in kivy.properties.Property.set
   File "kivy\properties.pyx", line 606, in kivy.properties.Property._dispatch
   File "kivy\_event.pyx", line 1307, in kivy._event.EventObservers.dispatch
   File "kivy\_event.pyx", line 1213, in kivy._event.EventObservers._dispatch
   File "C:\Users\Александр\AppData\Local\Programs\Python\Python38-32\lib\site-packages\kivy\uix\screenmanager.py", line 1063, in on_current
     self.transition.start(self)
   File "C:\Users\Александр\AppData\Local\Programs\Python\Python38-32\lib\site-packages\kivymd\uix\transition\transition.py", line 87, in start
     {"in": self.animated_hero_in, "out": self.animated_hero_out}[
   File "C:\Users\Александр\AppData\Local\Programs\Python\Python38-32\lib\site-packages\kivymd\uix\transition\transition.py", line 149, in animated_hero_out
     hero_from_children = self._hero_from_widget_children[
 KeyError: 'New obj'

Screenshots

Expected result after clicking on the 'Add New' button and going to screen B. image

What is really image

Versions

mrTokar commented 1 year ago

I found what the problem is and fixed this error. The thing is, when you add MDHero to your application, KivyMD automatically adds some data about the Hero to memory when the application starts. And after when you clear the container and add Heroes there again, this special data will not be updated. That is why it is necessary to do this operation.

class Container(MDScreenManager):
    ...
    def update_hero_data(self, widget):
        self._heroes_data = []
        self._create_heroes_data(widget)

class HeroApp(MDApp):
    ...
    def clear(self):
        self.container.scr_a.box.clear_widgets()
        self.container.update_hero_data(self.container.scr_a)
andchir commented 5 months ago

@mrTokar I think a better (temporary) solution is this:

def switch_screen(*args):
    if len(self.manager._heroes_data) == 0:  # waiting for the hero to appear
        Clock.schedule_once(self.switch_screen, 0.2)
        return
    self.scr_manager.current_heroes = [self.tag]
    scr_b.hero_to.tag = self.tag
    self.scr_manager.current = "scr_b"