Search code examples
pythonkivykivy-languagepython-class

How to pass a variable from Class to another Class?


I am using Kivy and Kivymd. I have a problem with passing a variable between class Admin and class EditArticle. I need to pass my_string from Admin to EditArticle. I am trying to do this, but getting an empty string. So, in class Admin I have my_string. Then, in a method edit_article of class Admin i am setting value 'some text' for my_string. Then I am trying to get it in the method edit of class EditArticle. But it is empty all the time. I really cannot figure it out.

  1. If you run my code you will click on top menu admin.
  2. Then to click on any mdchip.
  3. Then to click on etit button in dialog window.
  4. Then to click on button to get my_string (but it always empty).

This is my App.py

from kivy.clock import Clock
from kivymd.app import MDApp
from kivy.properties import StringProperty, NumericProperty
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.boxlayout import BoxLayout
from kivymd.uix.card import MDCard
from kivymd.uix.chip import MDChip
from kivy.core.window import Window
Window.size = (600, 853)
from kivymd.uix.menu import MDDropdownMenu
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import RectangularElevationBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDFlatButton
from kivymd.uix.dialog import MDDialog

class WindowManager(ScreenManager):
    pass

class AdminFooter(ThemableBehavior, RectangularElevationBehavior, MDBoxLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.md_bg_color = self.theme_cls.primary_color

class ToolbarBack(ThemableBehavior, RectangularElevationBehavior, MDBoxLayout):
    pass

class CustomToolbar(ThemableBehavior, RectangularElevationBehavior, MDBoxLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.md_bg_color = self.theme_cls.primary_color

        Clock.schedule_once(self.create_menu, 1)

    def create_menu(self, i):
        self.items = ['admin', 'settings']
        menu_items = [{"text":  f"{i}"} for i in self.items]
        self.menu = MDDropdownMenu(
            caller=self.ids.button_2, items=menu_items, width_mult=4, callback=self.get_item
        )

    def get_item(self, instance):
        self.menu.dismiss()
        App.get_running_app().window_manager.current = instance.text

class Content(BoxLayout):
    pass

class Admin(Screen):

    dialog_get_article = None

    my_string = StringProperty() # My string

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def get_article(self, instance,*args): 
        self.dialog_get_article = MDDialog(
            title="Click the EDIT",
            buttons=[
                MDFlatButton(
                    text="EDIT",
                    on_release = self.edit_article
                ),
            ],
        )
        self.dialog_get_article.open()

    def edit_article(self, instance):
        self.my_string = 'some text' # set value
        App.get_running_app().window_manager.current = 'edit-article'
        self.dialog_get_article.dismiss()

    def on_enter(self, *args):
        data = [
            {'id': 1, 'title': 'Arcicle 1', 'body': 'body of Article 1'},
            {'id': 2, 'title': 'Arcicle 2', 'body': 'body of Article 2'},
            {'id': 3, 'title': 'Arcicle 3', 'body': 'body of Article 3'}
        ]
        for x in data:
            chip = BlogChip(id=x.get('id'), title=x.get('title'), body=x.get('body'))
            self.ids.box.add_widget(chip)

class EditArticle(Screen):

    var = StringProperty()

    def edit(self, instance):
        print(self.var, ' << it is goingt to be <my_string> from Admin')

class UserSettings(Screen):
    pass

class BlogChip(MDChip):

    get_admin = Admin()

    id = NumericProperty()
    title = StringProperty()
    body = StringProperty()

class BlogCard(MDCard):
    pass

class Detail(Screen):
    pass

class ResultSearch(Screen):
    pass

class Container(Screen):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        Clock.schedule_once(self.create_cards)

    def create_cards(self, i):
        pass

class App(MDApp):

    def callback(self):
        self.window_manager.current = 'container'

    def build(self):
        self.theme_cls.primary_palette = 'Indigo'
        self.window_manager = WindowManager()
        return self.window_manager

App().run()

My app.kv

 <WindowManager>
    Container:
        id: scr_1
        name: 'container'
    Detail:
        id: scr_2
        name: 'detail'

    Admin:
        id: scr_3
        name: 'admin'
    EditArticle:
        id: scr_4
        name: 'edit-article'
        var: scr_3.my_string # <---------

    ResultSearch:
        id: scr_5
        name: 'result-search'
    UserSettings:
        id: scr_6
        name: 'settings'

<Admin>:
    BoxLayout:
        id: boxlayout_1
        orientation: 'vertical'

        MDToolbar:
            pos_hint: {'top': 1}
            title: 'Admin Blog'
            left_action_items: [["arrow-left", lambda x: app.callback()]]

        ScrollView:
            MDStackLayout:
                adaptive_height: True
                padding: 10
                spacing: dp(5)
                id: box

<EditArticle>
    MDToolbar:
        title: 'Admin Blog'
    MDLabel:
        text: str(root.var)
    MDRaisedButton:
        text: 'click me to see a variable in console'
        pos_hint: {'center_x': .5, 'center_y': .5}
        on_release: root.edit(root)

<MyToolbar@CustomToolbar>:
    size_hint_y: None
    height: self.theme_cls.standard_increment
    padding: "25dp"
    spacing: "12dp"

    MDLabel:
        id: label
        text: 'Blog'
        font_style: 'H6'
        theme_text_color: "Custom"
        text_color: 1,1,1,1

    Widget:

    MDIconButton:
        id: button_2
        icon: "dots-vertical"
        pos_hint: {"center_y": .5}
        theme_text_color: "Custom"
        text_color: 1,1,1,1
        on_release: root.menu.open()

<Container>
    BoxLayout:
        orientation: 'vertical'
        MyToolbar:
        MDLabel:
            text: 'Go To menu (dot-vertical/admin'
            halign: 'center'

<BlogCard>

<Detail>:

<BlogChip>
    label: root.title
    icon: ''
    callback: root.get_admin.get_article

Solution

  • Root Cause:

    In your app, there are two instances of class Admin instantiated.

    This first instance was instantiated in the kv file when you instantiated the root, WindowManager() in the build method.

    Snippets:

    <WindowManager>:
        ...
    
        Admin:
            id: scr_3
            name: 'admin'
        ...
    

    The second instance was instantiated in the class BlogChip.

    Snippets:

    class BlogChip(MDChip):
        get_admin = Admin()
        ...
    

    The class attribute, my_string was updated in the second instance of class Admin. But the screen is referencing the instance instantiated by the root. Therefore, the app displayed empty string.

    Solution:

    The solution requires changes to the kv and python files.

    kv file:

    Replace callback: root.get_admin.get_article with callback: app.root.ids.scr_3.get_article

    Snippets:

    <BlogChip>
        label: root.title
        icon: ''
        callback: app.root.ids.scr_3.get_article
    

    Python file:

    1. Remove get_admin = Admin() in class BlogChip(MDChip):

    Snippets:

    class BlogChip(MDChip):
        id = NumericProperty()
        title = StringProperty()
        body = StringProperty()
    
    1. In method, edit() initialize class attribute, var

    Snippets:

    class EditArticle(Screen):
        var = StringProperty()
    
        def edit(self, instance):
            # Each screen has by default a property manager that 
            # gives you the instance of the ScreenManager used.
            self.var = self.manager.ids.scr_3.my_string
            print(self.var, ' << it is going to be <my_string> from Admin')
    

    Output:

    enter image description here