Search code examples
pythonpython-3.xkivykivy-languagekivymd

kivy + python: how to update form label based on external function response in multi-screen configuration


Hi to all kivy/python experts. I am looking for an advice/solution, after trying everything I found for the last week.

Here's my scenario:

  1. I have a kivy app with 2 screens, therefore I am using a Screen manager, with each screen (1 and 2) defined in my .kv file
  2. after clicking log-in on my 1st screen, app jumps to 2nd one. In this second screen, I have a button and a label (keeping it simple for the example). Now the issue:
  • when clicking on the button, I am calling a method foo1() which is in the same class: Screen2. foo1() is called on_press with a specific argument (from within .kv file, using on_press = root.foo1(arg))
  • based on the arg received, the function calculates something, then calls another function foo2(), passing the result. The foo2() is located in another python file(external.py), within another class (so we have different file, different class and in that class a method: foo2). All good so far
  • after finalizing some calculations, foo2() should return the result. Where? Well, inside my kivy label, on Screen2.

Problem encountered:

When trying to write the result from foo2() into my text label (my_label) in screenTwo, **foo2() **fails to instantiate the ids of the screen 2 (throwing all sorts of errors, depending on the things I tried (see below).

I can understand the reason: The moment I am calling foo2() from foo1() we are exiting the screenTwo as a parent for my_label, which changes the "self" context (of self.ids.my_label.text = result from foo2()). This now no longer refers to screenTwo, but probably to the in-memory address of the foo2() function which I think now acts as a parent (at least this is what I concluded, I am still a beginner in python).

Things I tried:

Basically everything I could find, to try and find the "real" parent that I need to enumerate, to find the children IDs where my_label actually is, but wasn't able to:

  1. tried declaring an ObjectProperty = None inside my Screen2 class, then give an id to my label and then a variable name which refers to that id inside my Screen2 class. This is recommended by many tutorials, but this is working when passing things between screens, not in my case.

  2. tried changing (inside foo2()) the reference to the destination of the label, from self.ids to

  • root.self.ids.my_label.text
  • root.manager.get_current('screenTwo').ids.my_label.text
  • main.root.manager.get_current('screenTwo').ids.my_label.text
  • app.main.root.manager.ids('screenTwo').ids.my_label.text
  • and maaaany other... :(

Depending on what I've tried (many tries) I received various errors. Here's some:

- kivy AttributeError: 'super' object has no attribute 'getattr' thread here also

- NameError: name 'root' is not defined

- AttributeError: 'str' object has no attribute 'text'

- Kivy AttributeError: 'NoneType' object has no attribute 'ids'

I do not seem to understand how this self/root/app context works, despite looking up at various resources (stackoverflow, pdf files, kivy official documentation) (if someone could recommend a good tutorial or explain for all newbies like me out there...wow how helpful it would be).

Also, I couldn't find anything related to whether this is actually possible, considering that from within the class that holds the current screen you're actually calling an external function located in another py file: **does kivy even support passing down responses from external functions back to the main function? **(I assume it does, since this is pure python, not kivy. kivy just manages the writing, within the correct context....if I were just able to figure it out :( ).

Anyway, here's my sample code (py + kv file). If any of you could give me a hint or a solution to how I could call a function which calls an external function which then writes the response back on the screen from which I started the events, in a label, I would be very thankful!

main.py

from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.app import MDApp
from kivy.app import App
from kivymd.uix.label import MDLabel
from kivy.properties import ObjectProperty, StringProperty
import external


class Screen1Main(Screen):
    pass

class Screen2(Screen):
    def foo1():
        # 1. do something 
        myArg = "x"

        # 2. then call foo2()
        external.myExternalClass.foo2(myArg)


class WindowManager(ScreenManager):
    pass



class MainApp(MDApp):
    def __init__(self, **kwargs):
        self.title = "My Application"
        super().__init__(**kwargs)


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

external.py

from kivy.app import App
import main

class myExternalClass:

    def foo2(arg1):

        #1. does something
            blabla
            
        #2. gets the result
            myResult = "anything"
       

       #3. tries to write the result into my_label (located in Screen 2, which is a child 
       # of my main app (in file: main.py), who manages the multi-screen via a screen manager within 
       # WindowsManager class)
        
       Screen2.manager.get_screen("screenTwo").ids.my_label.text = myResult    

---

main.kv

WindowManager:
    Screen1Main:
        id: id_screenOne
    Screen2:
        id: id_screenTwo
        

<Screen1Main>:
    name: "screenOne"
    GridLayout:
       <rest of layout here>

<Screen2>:
    name: "screenTwo"

    GridLayout:
        cols: 2        
        MDLabel:
            id: my_label
            text: "-"

        MDIconButton:
            id: my_button
            icon: "message-arrow-left"
            on_release: root.foo1(arg0)


# Things I tried:
Basically everything I could find, as described above.


Solution

  • Try using:

    MDApp.get_running_app().root.get_screen('screenTwo').ids.my_label.text = myResult
    

    in your foo2() method. This gets the MDApp and references its root (the WindowManager) to get the the desired Screen.