Writing a recipe app to play with, I got to the point of when you click on the recipe to see instructions/ingredients as a different screen it fails out completely. It originally was a popup but the formatting was an issue.
.kv file:
ScreenManager:
id: screen_manager
RecipeTableScreen:
name: 'recipe_table'
RecipeDetailScreen:
name: 'recipe_detail'
<RecipeTableScreen>:
name: 'recipe_table'
ScrollView:
size_hint: (1, None)
size: (root.width, root.height)
GridLayout:
id: recipe_layout
cols: 3
padding: 10
spacing: 10
size_hint_y: None
height: self.minimum_height
# Add headers
Label:
text: 'Name'
bold: True
size_hint_y: None
height: 40
Label:
text: 'Category'
bold: True
size_hint_y: None
height: 40
Label:
text: 'Rating'
bold: True
size_hint_y: None
height: 40
<RecipeDetailScreen>:
name: 'recipe_detail'
BoxLayout:
orientation: 'vertical'
padding: 10
spacing: 10
Label:
id: recipe_name
text: ''
bold: True
font_size: 24
Label:
text: 'Ingredients:'
bold: True
BoxLayout:
orientation: 'vertical'
id: ingredients_box
padding: 8
spacing: 8
Label:
text: 'Instructions:'
bold: True
ScrollView:
size_hint: (1, None)
size: (root.width * 0.7, root.height * 0.4)
Label:
id: instructions_label
text_size: (self.width, None)
size_hint_y: None
height: self.texture_size[1]
Button:
text: 'Back'
size_hint_y: None
height: 40
on_release:
app.root.current = 'recipe_table'
.py file:
import json
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty, StringProperty
class RecipeTableScreen(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.load_recipes()
def load_recipes(self):
# Load recipes from JSON file
with open('recipes.json', 'r') as file:
recipes = json.load(file)
# Load recipe details from JSON file
with open('recipe_details.json', 'r') as file:
self.recipe_details = json.load(file)
layout = self.ids.recipe_layout
# Add rows of recipes
for recipe in recipes:
button = Button(text=recipe['name'], size_hint_y=None, height=40)
button.bind(on_release=lambda btn: self.show_recipe_details(btn.text))
layout.add_widget(button)
layout.add_widget(Label(text=recipe['category'], size_hint_y=None, height=40))
layout.add_widget(Label(text=recipe['rating'], size_hint_y=None, height=40))
def show_recipe_details(self, recipe_name):
details = self.recipe_details.get(recipe_name, {})
ingredients = details.get('ingredients', [])
instructions = details.get('instructions', 'No instructions available.')
recipe_detail_screen = self.manager.get_screen('recipe_detail')
recipe_detail_screen.ids.recipe_name.text = recipe_name
ingredients_box = recipe_detail_screen.ids.ingredients_box
ingredients_box.clear_widgets()
for ingredient in ingredients:
ingredients_box.add_widget(Label(text=ingredient))
recipe_detail_screen.ids.instructions_label.text = instructions
self.manager.current = 'recipe_detail'
class RecipeDetailScreen(Screen):
pass
class RecipeTableApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(RecipeTableScreen(name='recipe_table'))
sm.add_widget(RecipeDetailScreen(name='recipe_detail'))
return sm
if __name__ == '__main__':
RecipeTableApp().run()
Here is the error:
Traceback (most recent call last):
File "kivy\\properties.pyx", line 961, in kivy.properties.ObservableDict.__getattr__
KeyError: 'recipe_layout'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\X\OneDrive\Recipe_App\RecipeAppv2.py", line 62, in <module>
RecipeTableApp().run()
File "C:\Users\X\OneDrive\Recipe_App\kivy_venv\Lib\site-packages\kivy\app.py", line 955, in run
self._run_prepare()
File "C:\Users\X\OneDrive\Recipe_App\kivy_venv\Lib\site-packages\kivy\app.py", line 924, in _run_prepare
self.load_kv(filename=self.kv_file)
File "C:\Users\X\OneDrive\Recipe_App\kivy_venv\Lib\site-packages\kivy\app.py", line 697, in load_kv
root = Builder.load_file(rfilename)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X\OneDrive\Recipe_App\kivy_venv\Lib\site-packages\kivy\lang\builder.py", line 310, in load_file return self.load_string(data, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X\OneDrive\Recipe_App\kivy_venv\Lib\site-packages\kivy\lang\builder.py", line 412, in load_string
self._apply_rule(
File "C:\Users\X\OneDrive\Recipe_App\kivy_venv\Lib\site-packages\kivy\lang\builder.py", line 663, in _apply_rule
child = cls(__no_builder=True)
^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\X\OneDrive\Recipe_App\RecipeAppv2.py", line 15, in __init__
self.load_recipes()
File "C:\Users\X\OneDrive\Recipe_App\RecipeAppv2.py", line 26, in load_recipes
layout = self.ids.recipe_layout
^^^^^^^^^^^^^^^^^^^^^^
File "kivy\\properties.pyx", line 964, in kivy.properties.ObservableDict.__getattr__
AttributeError: 'super' object has no attribute '__getattr__'. Did you mean: '__setattr__'?
If I add an attribute to super it failes out with 'super() takes no keywork arguments' I have absolutely no idea why this is failing out now, what am I missing here?
The problem is that you are trying to access the ids
of the RecipeTableScreen
before they are assigned. From the documentation:
Note that the outermost widget applies the kv rules to all its inner widgets before any other rules are applied. This means if an inner widget contains ids, these ids may not be available during the inner widget’s init function.
A simple fix is to delay the access until after the ids
are assigned. The Clock.schedule_once()
method is handy here. Try changing the __init__()
method to:
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self.load_recipes)
# self.load_recipes()
And a minor change to the load_recipes()
method to handle the dt
parameter that Clock.schedule_once()
passes:
def load_recipes(self, _dt):