Search code examples
pythonpython-3.xkivykivy-languagekivymd

How do I pass an argument between KivyMD screens?


When going from the first screen to the second screen, I want to pass a variable as an argument so that kivyMD can update the second screen from text stored in an excel file. The following is a skeleton of my app's functionality:

The user reaches Screen 1 thru the navigation drawer in KivyMD, screen 1 presents the user with two options on two small clickable MDCards:

  • "Change text to 1"
  • "Change text to 2"

After clicking on one of these, the app switches to screen 2 with a single big MDCard, the text on this MDCard should change to reflect the option the user chose.

However, kivy is pulling the text that is to be displayed on the big MDCard from an excel file. The variable that I want to pass from screen 1 to screen 2 is simply a number (1 or 2) that will tell kivy which row in the excel file it should pull the text from

If the user clicks "Change text to 1" then the first screen should pass "1" as the argument row_x to the function def change_text() (see screen 2 .py) so that the text in row 1 of excel can be displayed on the second screen. How can I achieve this?

I have 4 files in total; 3 are .py files (one for the main app, one for screen 1, and one for screen 2), and the excel file

NOTE: in the code below, Screen 1 & 2 are called Element 1 & 2 respectfully

Main.py:

from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
from kivymd.app import MDApp
from element_1 import element_1_screen
from element_2 import element_2_screen

MainNavigation = '''
<ContentNavigationDrawer>:
    ScrollView:
        MDList:
            OneLineListItem:
                text: 'Go to Element 1'
                on_press:
                    root.nav_drawer.set_state("close")
                    root.screen_manager.current = "go_to_element_1_screen"

Screen:
    MDToolbar:
        id: toolbar
        pos_hint: {"top": 1}
        elevation: 10
        left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]]
    MDNavigationLayout:
        x: toolbar.height
        ScreenManager:
            id: screen_manager
            Screen:
                name: "words_nav_item"
            element_1_screen:
                name: "go_to_element_1_screen"
            element_2_screen:
                name: "go_to_element_2_screen"

        MDNavigationDrawer:
            id: nav_drawer
            ContentNavigationDrawer:
                screen_manager: screen_manager
                nav_drawer: nav_drawer
'''


class ContentNavigationDrawer(BoxLayout):
    screen_manager = ObjectProperty()
    nav_drawer = ObjectProperty()


class mainApp(MDApp):
    def build(self):
        self.theme_cls.primary_palette = "Red"
        return Builder.load_string(MainNavigation)


mainApp().run()

Screen 1 / Element 1

from kivy.lang import Builder
from kivymd.uix.screen import MDScreen

element_1_contents = '''
<element_1_screen>:
    MDGridLayout:
        rows: 2
        size: root.width, root.height
        pos_hint: {"center_x": .8, "center_y": .2}
        spacing: 40
        MDCard:
            orientation: 'vertical'
            size_hint: None, None
            size: "360dp", "120dp"
            ripple_behavior: True
            on_release:
                root.manager.current = "go_to_element_2_screen" 
            MDLabel:
                id: LabelTextID
                text: "Change Text to 1"
                halign: 'center'
    
        MDCard:
            orientation: 'vertical'
            size_hint: None, None
            size: "360dp", "120dp"
            ripple_behavior: True
            on_release:
                root.manager.current = "go_to_element_2_screen" 
            MDLabel:
                id: LabelTextID
                text: "Change Text to 2"
                halign: 'center'
                
        
'''

class element_1_screen(MDScreen):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        Builder.load_string(element_1_contents)

Screen 2 / Element 2

from kivy.lang import Builder
from kivymd.uix.screen import MDScreen
import openpyxl

element_2_contents = '''
<element_2_screen>:
    MDCard:
        orientation: 'vertical'
        size_hint: None, None
        size: "360dp", "360dp"
        pos_hint: {"center_x": .5, "center_y": .5}
        ripple_behavior: True
        focus_behavior: True
        on_release: root.manager.current = "go_to_element_1_screen"

        MDLabel:
            id: TextID
            text: "NOTHING HAS CHANGED"
            halign: 'center'

        MDLabel:
            text: "(Click here to return)"
            halign: 'center'
            
'''

class element_2_screen(MDScreen):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        path = "data.xlsx"
        self.wb_obj = openpyxl.load_workbook(path)
        self.sheet_obj = self.wb_obj.active
        Builder.load_string(element_2_contents)

    def change_text(self, row_x=0):
        row_number = self.sheet_obj.cell(row_x, column=1)
        self.ids.TextID.text = str(row_number.value)

And the excel file only has two entries in Column A:

Row 1: You have chosen 1
Row 2: You have chosen 2

Solution

  • I found the answer and now it works flawlessly. Someone over on Reddit (u/Username_RANDINT) helped me, this is what they said:

    The ScreenManager has a get_screen() method. You could use it to get the instance of the second screen and call the change_text() method on that. In the same place where you switch screens, add another line:

        on_release:
            root.manager.current = "go_to_element_2_screen"
            root.manager.get_screen("go_to_element_2_screen").change_text(1)
    

    Then the same for the other card, just pass in 2 instead of 1.