pythonkivykivy-language

Kivy Screen Manager kivy\properties.pyx KeyError and AttributeError: 'super' object has no attribute '__getattr__'. Did you mean: '__setattr__'?


I've been encountering an issue when trying to implement screen manager with Kivy. As you can see the 'chat' id is defined in main.kv so I can't understand why the error is occurring. I'm new with Kivy and have never used screen manager before.. please help!

app_cleaned.py

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.label import Label
from kivy.core.window import Window
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.clock import mainthread

from range_key_dict import RangeKeyDict

from math import inf

from threading import Thread

from powerBot import ChatBot

#remove
from kivy.clock import Clock

Window.size = (500, 500)

class SignupScreen(Screen):
    pass

class MainScreen(Screen):
    pass

class Message(Label):
    pass

class User:
    def __init__(self, years_lifting, weight, height, unavailable_equipment, unavailable_muscle_groups, aim):

        # Lifting experience classification
        expClassDict = RangeKeyDict({
            (0.0, 1.9): 1, # beginner
            (2.0, 3.9): 2, # intermediate
            (4, inf): 3 # advanced
        })
        expClass = expClassDict[years_lifting]

        # BMI classification
        bmiClassDict = RangeKeyDict({
            (0.0, 9.9): 1, # severely underweight
            (10.0, 18.5): 2, # underweight
            (18.6, 24.9): 3, # healthy weight
            (25.0, 34.9): 2, # overweight
            (35.0, inf): 1, # severely overweight
        })
        bmi = weight/(height/100)**2 # calculate BMI
        bmiClass = bmiClassDict[bmi]

        self.experience_level = expClass
        self.bmi_level = bmiClass
        self.unavailable_equipment = unavailable_equipment
        self.unavailable_muscle_groups = unavailable_muscle_groups
        self.aim = aim



class ExampleApp(App):
    def build(self):
        sm = ScreenManager()

        # Load the signup.kv file and add its content to the SignupScreen
        Builder.load_file('signup.kv')

        # Create the SignupScreen instance and add it to the ScreenManager
        signup_screen = SignupScreen(name='signup')
        sm.add_widget(signup_screen)
        
        # Load the main application screen from main.kv
        Builder.load_file('main.kv')
        main_screen = MainScreen(name='main')
        sm.add_widget(main_screen)

        return sm

    def switch_to_main_screen(self):
        self.root.current = 'main'
    
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.ai = ChatBot("PowerBot")

    def on_start(self):
        print("Root IDs:", self.root.ids)
        # Assuming you expect 'chat' to be available here, you can print its contents as well
        if 'chat' in self.root.ids:
            print("Chat Widget:", self.root.ids.chat)
        self.root.current = 'signup'
        initial_messages = ["PowerBot is initializing, please wait (this could take a minute)"]
        for message in initial_messages:
            self.system_message(message)
        message = "000000"
        thread = Thread(target=self.background_message_receiver,args=(message,))
        thread.start()
        self.root.ids.sv.scroll_y = 0

    def sign_up(self, years_lifting, weight, height):
        # Convert input values to appropriate data types (e.g., int, float)
        years_lifting = float(years_lifting)
        weight = float(weight)
        height = float(height)

        # Create a User object with the provided sign-up details
        user = User(years_lifting, weight, height)  # Pass other sign-up details as needed

        self.root.ids.years_lifting_input.text = ''
        self.root.ids.weight_input.text = ''
        self.root.ids.height_input.text = ''

    def background_message_receiver(self, message):
        response = self.ai.message_to_bot(message)
        self.incoming_message(response)

    def send_message(self, message):
        self.root.ids.ti.text = ""
        if message:
            m = Message(text=f"[color=dd2020]You[/color] > {message}")
            self.root.ids.chat.add_widget(m)
            self.root.ids.ti.focus = True
            thread = Thread(target=self.background_message_receiver,args=(message,))
            thread.start()

    @mainthread
    def incoming_message(self, message):
        m = Message(text=f"[color=20dd20]PowerBot[/color] > {message}")
        self.root.ids.chat.add_widget(m)
        self.root.ids.ti.focus = True

    def system_message(self, message):
        m = Message(text=f"[color=ffffff]System[/color] > {message}")
        self.root.ids.chat.add_widget(m)
        self.root.ids.ti.focus = True
    
# Execute
if __name__ == '__main__':
    ExampleApp().run()

main.kv

<Message>:
    size_hint: 1, None
    text_size: self.width, None
    size: self.texture_size
    markup: True
    
<MainScreen>:
    BoxLayout:
        orientation: 'vertical'
        padding: 10
        ScrollView:
            id: sv
            BoxLayout:
                id: chat  # Add the ID for the chat messages
                spacing: 5
                padding: 10
                orientation: 'vertical'
                size_hint_y: None
                height: self.minimum_height
                Widget: # used as a spacer, push message to bottom
                    size_hint_y: None
                    height: sv.height
        BoxLayout:
            size_hint_y: None
            height: 40
            spacing: 10
            TextInput:
                id: ti
                multiline: False
                on_text_validate: app.send_message(self.text)
            Button:
                text: 'submit'
                size_hint_x: None
                width: 75
                on_release: app.send_message(ti.text)

signup.kv

<SignupScreen>:
    BoxLayout:
        orientation: 'vertical'
        padding: 10
        
        Button:
            text: 'Continue'
            size_hint_y: None
            height: '48dp'
            on_release: app.switch_to_main_screen()

I have seen the other question on here regarding a similar issue but unfortunately I'm not proficient enough with Kivy to apply the solutions to my specific scenario.


Solution

  • Your chat id is defined in the MainScreen class, but you are trying to access it in self.root.ids of the ExampleApp. Since that id is defined in the <MainScreen> rule, you must access it through the MainScreen instance. One way to do that is to just save a reference to the MainScreen instance. In your build() method you can change:

        # Load the main application screen from main.kv
        Builder.load_file('main.kv')
        main_screen = MainScreen(name='main')
        sm.add_widget(main_screen)
    

    to:

        # Load the main application screen from main.kv
        Builder.load_file('main.kv')
        self.main_screen = MainScreen(name='main')
        sm.add_widget(self.main_screen)
    

    Then, anywhere inside any non-static ExampleApp method, you can access the chat id as:

    self.main_screen.ids.chat