'NoneType' object is not subscriptable using kv - kivy

I'm trying to make a phone app, and want to be able to navigate between multiple screens.
However I don't know what to do with the syntax error that keeps being returned.
I'm new to kivy, and looked at a ton of tutorial stuff to put my code together, help would be much appreciated.
Here is code on main file
from kivy.uix.screenmanager import ScreenManager, Screen #screen manager is a widget dedicated to managing multiple screens for your application.
from kivy.uix.button import Button #creates the button in kivy
from kivy.uix.floatlayout import FloatLayout #means can place elements based on windows size
from kivy.config import Config
from kivy.properties import ObjectProperty
from kivy.uix.popup import Popup
from kivy.uix.widget import Widget
from kivy.uix.label import Label
Config.set('graphics', 'resizable', True)
kv = Builder.load_file("phoneassistant.kv") #links to kv file where graphics are loaded
sm = WindowManager() #sm will control moving between screens using the ScreenManager class
#creating classes for all screen on app help specify methods for individual screens using argument Screen
class LoginWindow(Screen, FloatLayout):
def mainBtn(self):
self.reset()
sm.current = "main"
def createBtn(self):
self.reset()
sm.current = "create"
class CreateAccountWindow(Screen, FloatLayout):
def loginBtn(self):
self.reset()
sm.current = "login"
class MainWindow(Screen, FloatLayout):
def loginBtn(self):
self.reset()
sm.current = "login"
def settingsBtn(self):
self.reset()
sm.current = "settings"
def storageBtn(self):
self.reset()
sm.current = "storage"
class SettingsWindow(Screen, FloatLayout):
pass
class StorageWindow(Screen, FloatLayout):
pass
class AIWindow(Screen, FloatLayout):
pass
class WindowManager(ScreenManager): #this won't be a sceen, ScreenManager is to manage transitions between all screens
pass
#gives the screens names
screens = [LoginWindow(name = "login"), CreateAccountWindow(name = "create"), MainWindow(name="main"),
SettingsWindow(name = "settings"),StorageWindow(name = "storage"), AIWindow(name = "AI")]
#adds the screens to the manager
for screen in screens:
sm.add_widget(screen)
#sends user to login page whenever program is ran
sm.current = "login"
class PhoneAssistantApp(App): #creates the app class
def build(self):
return sm #returns screens when class is ran
if __name__ == "__main__": #runs the app
PhoneAssistantApp().run()
The code on the kv file
<ScreenManagement>:
transition: SlideTransition
LoginWindow:
CreateAccountWindow:
MainWindow:
SettingsWindow:
StorageWindow:
AIWindow:
<LoginWindow>:
name: "login"
    FloatLayout:
Button:
         text: "Don't have an account?"
font_size: 0.2, 0.1
            background_color: 0, 0, 1, 1
size_hint: 0.4, 0.3
pos: {"x":0.5, "y":0.25}
            on_release:
                root.manager.transition.direction = "left"
                root.manager.transition.duration = 1
root.createBtn()
Button:
            text: "Login here"
font_size: 0.1, 0.2
            background_color: 1, 0, 0, 1
size_hint: 0.4, 0.3
pos_hint: {"x":0.5, "y":0.75}
            on_release:
                root.manager.transition.direction = "right"
                root.manager.transition.duration = 1
                root.mainBtn()
  
<CreateAccountWindow>:
name: "create"
    FloatLayout:
        Button:
            text: "Create login"
font_size: 0.25, 0.25
            background_color : 1, 1, 0, 1
size_hint: 0.5, 0.5
pos_hint: {"x":0.5, "y":0.1}
            on_release:
                app.root.current = "login"
                root.manager.transition.direction = "right"
                root.manager.transition.duration = 1
 
<MainWindow>:
name: "main"
    FloatLayout:
        Button:
            text: "Return to login page"
font_size: 0.25, 0.25
            background_color : 1, 1, 0, 1
size_hint: 0.25, 0.35
pos_hint: {"x":0.5, "y":0.1}
            on_release:
                app.root.current = "login"
                root.manager.transition.direction = "left"
                root.manager.transition.duration = 1
        Button:
            text: "Access Settings"
font_size: 0.5, 0.5
            background_color : 1, 0, 1, 1
size_hint: 0.35, 40.5
pos_hint: {"x":0.1, "y":0.9}
            on_release:
                app.root.current = "settings"
                root.manager.transition.direction = "right"
                root.manager.transition.duration = 1
        Button:
            text: "Access Storage"
font_size: 0.75, 0.75
            background_color : 1, 1, 1, 0
size_hint: 0.45, 0.25
pos_hint: {"x":0.9, "y":0.5}
            on_release:
                app.root.current = "storage"
                root.manager.transition.direction = "right"
                root.manager.transition.duration = 1
 
<SettingsWindow>:
name: "settings"
    FloatLayout:
        Button:
            text: "Return to Main Menu"
            background_color: 0, 1, 1, 1
            on_release:
                app.root.current = "main"
                root.manager.transition.direction = "left"
                root.manager.transition.duration = 1
 
<StorageWindow>:
name: "storage"
FloatLayout:
        Button:
            text: "Return to Main Menu"
font_size: 0.25, 0.25
            background_color : 1, 1, 1, 0
size_hint: 0.5, 0.5
pos_hint: {"x":0.1, "y":0.1}
            background_color: 1, 0, 0, 1
            on_release:
                app.root.current = "main"
                root.manager.transition.direction = "left"
root.manager.transition.duration = 1
Button:
text: "Access AI"
font_size: 0.25, 0.25
            background_color : 1, 1, 1, 0
size_hint: 0.5, 0.5
pos_hint: {"x":0.9, "y":0.9}
background_color: 1, 1, 0, 0
on_release:
app.root.current = "ai"
root.manager.transition.direction = "right"
root.manager.transition.duration = 1
<AIWindow>
name: "ai"
FloatLayout:
Button:
text: "Return to Storage"
background_color: 0, 1, 1, 0
on_release:
app.root.current = "storage"
root.manager.transition.direction = "left"
root.manager.transition.duration = 1
and the error
Traceback (most recent call last):
line 15, in <module>
kv = Builder.load_file("phoneassistant.kv") #links to kv file where graphics are loaded
line 305, in load_file
return self.load_string(data, **kwargs)
line 372, in load_string
parser = Parser(content=string, filename=fn)
line 483, in __init__
self.parse(content)
line 593, in parse
objects, remaining_lines = self.parse_level(0, lines)
line 756, in parse_level
if current_property[:3] == 'on_':
TypeError: 'NoneType' object is not subscriptable
I have tried changing the modules I use, such as changing to BoxLayout to see if that would work
And changed the on_press, to on_release, and sometimes tried to have both in my kv file
In addition, added transition to screen management class in kv file
Also created the def statements in the login, create, and main, classes on the main program
These were all mostly because I thought the main problem was the on_release button, however it's like likely also the structure of my code.

I didn't see where your code would create that error, but possibly your kivy file has an indentation problem somewhere. Something could be over-indented or else missing such that there is no object present. The code below is runnable but I don't think it has the layout polished as you intended. I took the liberty of adding a few example methods to your LoginWindow that I thought you may want to use.
I was not sure what was meant by reset() as you didn't have matching code for this so i changed this just to something that prints to the console.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from kivy.lang import Builder
# screen manager is a widget dedicated to managing multiple screens for your application.
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.button import Button # creates the button in kivy
from kivy.uix.floatlayout import FloatLayout # means can place elements based on windows size
from kivy.config import Config
from kivy.properties import StringProperty, BooleanProperty, ListProperty, NumericProperty
from kivy.uix.popup import Popup
from kivy.uix.label import Label
from kivy.app import App
kv = Builder.load_string('''
# <ScreenManagement>:
# transition: SlideTransition
# LoginWindow:
# CreateAccountWindow:
# MainWindow:
# SettingsWindow:
# StorageWindow:
# AIWindow:
<LoginWindow>:
name: "login"
FloatLayout:
Button:
text: "Don't have an account?"
# font_size: 0.2, 0.1
background_color: 0, 0, 1, 1
size_hint: 0.4, 0.3
# pos: {"x":0.5, "y":0.25}
on_release:
root.manager.transition.direction = "left"
root.manager.transition.duration = 1
root.createBtn()
Button:
text: "Login here"
# font_size: 0.1, 0.2
background_color: 1, 0, 0, 1
size_hint: 0.4, 0.3
pos_hint: {"x":0.5, "y":0.75}
on_release:
root.manager.transition.direction = "right"
root.manager.transition.duration = 1
root.mainBtn()
<CreateAccountWindow>:
name: "create"
FloatLayout:
Button:
text: "Create login"
# font_size: 0.25, 0.25
background_color : 1, 1, 0, 1
size_hint: 0.5, 0.5
pos_hint: {"x":0.5, "y":0.1}
on_release:
app.root.current = "login"
root.manager.transition.direction = "right"
root.manager.transition.duration = 1
<MainWindow>:
name: "main"
FloatLayout:
Button:
text: "Return to login page"
# font_size: 0.25, 0.25
background_color : 1, 1, 0, 1
size_hint: 0.25, 0.35
pos_hint: {"x":0.5, "y":0.1}
on_release:
app.root.current = "login"
root.manager.transition.direction = "left"
root.manager.transition.duration = 1
Button:
text: "Access Settings"
# font_size: 0.5, 0.5
background_color : 1, 0, 1, 1
size_hint: 0.35, 40.5
pos_hint: {"x":0.1, "y":0.9}
on_release:
app.root.current = "settings"
root.manager.transition.direction = "right"
root.manager.transition.duration = 1
Button:
text: "Access Storage"
# font_size: 0.75, 0.75
background_color : 1, 1, 1, 0
size_hint: 0.45, 0.25
pos_hint: {"x":0.9, "y":0.5}
on_release:
app.root.current = "storage"
root.manager.transition.direction = "right"
root.manager.transition.duration = 1
<SettingsWindow>:
name: "settings"
FloatLayout:
Button:
text: "Return to Main Menu"
background_color: 0, 1, 1, 1
on_release:
app.root.current = "main"
root.manager.transition.direction = "left"
root.manager.transition.duration = 1
<StorageWindow>:
name: "storage"
FloatLayout:
Button:
text: "Return to Main Menu"
# font_size: 0.25, 0.25
background_color : 1, 1, 1, 0
size_hint: 0.5, 0.5
pos_hint: {"x":0.1, "y":0.1}
background_color: 1, 0, 0, 1
on_release:
app.root.current = "main"
root.manager.transition.direction = "left"
root.manager.transition.duration = 1
Button:
text: "Access AI"
# font_size: 0.25, 0.25
background_color : 1, 1, 1, 0
size_hint: 0.5, 0.5
pos_hint: {"x":0.9, "y":0.9}
background_color: 1, 1, 0, 0
on_release:
app.root.current = "ai"
root.manager.transition.direction = "right"
root.manager.transition.duration = 1
<AIWindow>
name: "ai"
FloatLayout:
Button:
text: "Return to Storage"
background_color: 0, 1, 1, 0
on_release:
app.root.current = "storage"
root.manager.transition.direction = "left"
root.manager.transition.duration = 1
''')
Config.set('graphics', 'resizable', True)
# creating classes for all screen on app help specify methods for individual screens using argument Screen
class LoginWindow(Screen, FloatLayout):
def on_pre_enter(self, *args):
print(" on pre-enter")
def on_enter(self, *args):
print("on enter")
def on_leave(self, *args):
print("login leave")
def on_pre_leave(self, *args):
print("pre-leave")
def reset(self):
print(f"reset {self}")
def mainBtn(self):
self.reset()
App.get_running_app().root.current = "main"
def createBtn(self):
self.reset()
App.get_running_app().root.current = "create"
class CreateAccountWindow(Screen, FloatLayout):
def reset(self):
print(f"{self}")
def loginBtn(self):
self.reset()
App.get_running_app().root.current = "login"
class MainWindow(Screen, FloatLayout):
def reset(self):
print(f"{self}")
def loginBtn(self):
self.reset()
App.get_running_app().root.current = "login"
def settingsBtn(self):
self.reset()
App.get_running_app().root.current = "settings"
def storageBtn(self):
self.reset()
App.get_running_app().root.current = "storage"
class SettingsWindow(Screen, FloatLayout):
pass
class StorageWindow(Screen, FloatLayout):
pass
class AIWindow(Screen, FloatLayout):
pass
class PhoneAssistantApp(App): # creates the app class
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.sm = ScreenManager() # sm will control moving between screens using the ScreenManager class
# gives the screens names
self.screens = (LoginWindow(name="login"), CreateAccountWindow(name="create"), MainWindow(name="main"),
SettingsWindow(name="settings"), StorageWindow(name="storage"), AIWindow(name="AI"))
def build(self) -> ScreenManager:
# adds the screens to the manager
for screen in self.screens:
self.sm.add_widget(screen)
# user to login page whenever program is ran
self.sm.current = "login"
return self.sm # returns screens when class is ran
if __name__ == "__main__":
PhoneAssistantApp().run()

Related

Positioning a text field inside a toolbar in Kivy

I'm trying to put a textfield inside a toolbar with KivyMD, but the textfield is always directly against the right side of the window. I tried to adjust it with pos_hint, but none of the values I put for the x-coordinates moved it.
Screen:
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: 'assets/bg.png'
BoxLayout:
orientation: 'vertical'
MDToolbar:
id: toolbar
title: 'Placeholder'
elevation: 10
pos_hint: {"top": 1}
left_action_items: [['menu', lambda x: app.menu.open()]]
MDTextFieldRound:
id: textinput
icon_left: 'magnify'
pos_hint: {'x': 0.1 ,'center_y': 0.5, 'right': 0.3}
size_hint: {.6, .4}
text_color: 'white'
hint_text: 'Search'**strong text**
MDLabel:
text: 'Placeholder'
color: 'white'
halign: 'center'
Screenshot of toolbar
Any help is appreciated, thanks.
You can insert your widgets wherever you want, for this you can specify a position in the widget tree - add_widget(widget, index). I wrote a special get_widgets function for your solution, which goes through all the widgets in the class. In the example below, you can place the text field on the left, right, and center. Also examine the MDToolbar source code
from kivy.lang.builder import Builder
from kivy.metrics import dp
from kivy.clock import Clock
from kivy.weakproxy import WeakProxy
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.textfield import MDTextField
KV = """
MDScreen:
MDBoxLayout:
orientation: 'vertical'
MDToolbar:
id: toolbar
title: 'Placeholder'
pos_hint: {"top": 1}
MDLabel:
text: 'Placeholder'
color: 'white'
halign: 'center'
"""
class TestApp(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.search_content = None
def build(self):
return Builder.load_string(KV)
def on_start(self):
Clock.schedule_once(lambda dt: self.add_search('left'))
#staticmethod
def get_widgets(root: WeakProxy, class_: str):
"""
:param root: root widget
:param class_: which subclass are we looking for
:return:
"""
widgets = []
for widget in root.walk():
if widget.__class__.__name__ == class_:
widgets.append(widget)
print(widget)
return widgets
def add_search(self, side: str):
"""
:param side: left/right/center
:return:
"""
box = self.get_widgets(self.root.ids.toolbar, 'MDBoxLayout')[0] # get root `MDBoxLayout`
if side == 'left':
index = 2
elif side == 'right':
index = 1
else:
index = 0
self.root.ids.toolbar.ids.label_title.size_hint = None, None
self.root.ids.toolbar.ids.label_title.opacity = 0
# NOTE: If you remove this `MDLabel`, you cant change `self.theme_cls.material_style`
# box.remove_widget(self.root.ids.toolbar.ids.label_title)
boxlayout = MDBoxLayout(padding=[dp(18), dp(0), dp(18), dp(18)])
self.search_content = MDTextField(icon_left='magnify',
mode='round',
color_mode="custom",
line_color_normal=(1, 0, 1, 1),
line_color_focus=(0, 0, 1, 1),
text_color_focus=self.theme_cls.text_color,
text_color_normal=self.theme_cls.text_color[0:3] + [0.7],
hint_text='Empty field',
)
boxlayout.add_widget(self.search_content)
box.add_widget(boxlayout, index)
TestApp().run()

How can a dynamic class access another class' attributes such as root.width/root.height?

Here is a class NewWindow with a GridLayout in a FloatLayout, a dynamic class NewButton inheriting from ToggleButton, and a class in the Python file that receives the GridProperty in order to add_widget NewButton into it.
The overall goal is to have a button that adds a toggle button into the grid layout, and for all button/toggle buttons to be able to dynamically change size for different screen resolutions. The issue I'm having is that I can't simply write (root.width**2 + root.height**2) / 12**4 for NewButton. I think it's because "root" in NewWindow is not the same as "root" in NewButton, but I do not have a solution.
Is there a way for me to access NewWindow's "root" inside of NewButton? I ultimately just want all my buttons to match each other's sizes and for it to be dynamic, so I don't want to simply write a static "30" for font_size.
# File: main.kv
<NewWindow>:
parentGrid: parentGrid
FloatLayout:
Button:
text: "Add New Button"
pos_hint: {"x": 0.05, "top": 0.7}
size_hint: .4, 0.05
font_size: (root.width**2 + root.height**2) / 12**4
on_release:
root.addButton()
GridLayout:
id: parentGrid
cols: 1
pos_hint: {"x": 0.5, "top": 0.7
size_hint: .4, .5
<NewButton#ToggleButton>:
text: "BUTTON"
size_hint_y: ???
#height: ???
font_size: ???
# Cannot simply use root.width or root.height, also size_hint: .4, 0.05 doesn't work
# File: MainApp.py
class NewWindow(Screen):
parentGrid = ObjectProperty(None)
def AddButton(self):
self.parentGrid.add_widget(NewButton())
You are right the two roots are different. One is as you say the size of the new window, but the other would only be the size of GridLayout. The problem you have with the size_hint is because you are using a GridLayout. If you can change to example a BoxLayout that would work if not the size_hint: None, None and then set the size: would work:
To get the the NewWindow Root use app.root.width and app.root.heigth
main.py:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.togglebutton import ToggleButton
class NewButton(ToggleButton):
pass
class NewWindow(Screen):
parentGrid = ObjectProperty(None)
def __init__(self, **kwargs):
super(NewWindow, self).__init__(**kwargs)
def addButton(self):
print('test')
self.parentGrid.add_widget(NewButton())
class Manager(ScreenManager):
pass
class TestApp(App):
def build(self):
return Builder.load_file('test.kv')
if __name__ == '__main__':
TestApp().run()
.kv:
Manager:
NewWindow:
name: 'new'
<NewButton#ToggleButton>:
text: "BUTTON18"
pos_hint: {"x": 0.05, "top": 0.7}
size_hint_x: None
size_hint: 1, 1
#size: 10, 10
font_size: (app.root.width**2 + app.root.height**2) / 12**4
<NewWindow>:
parentGrid: parentGrid
FloatLayout:
Button:
text: "Add New Button"
pos_hint: {"x": 0.05, "top": 0.7}
size_hint: .4, 0.05
font_size: (root.width**2 + root.height**2) / 12**4
on_release:
root.addButton()
BoxLayout:
id: parentGrid
cols: 1
pos_hint: {"x": 0.5, "top": 0.7}
size_hint: .4, .5
canvas:
Color:
rgba: 1,1,1,1
Rectangle:
size: self.size
pos: self.pos
# Cannot simply use root.width or root.height, also size_hint: .4, 0.05 doesn't work
I did also add the screenmanager to be able to access the width and height
Hope this helps

Move search items more to the top

I have the following code snippet displaying a popup Window.
class SearchBar(TextInput):
articles = ListProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(text=self.on_text)
self.bind(articles=self.on_articles)
def on_text(self, *args):
WikiSearcher().get_search_results(self.text, self)
def on_articles(self, *args):
self.parent.parent.children[0].update_recommendations(self.articles)
class SearchItem(ButtonBehavior, Label):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.url = ''
def on_release(self):
print (self.url)
class Recommendations(BoxLayout):
def update_recommendations(self, recommendations: list):
for (search_item, recommendation) in zip(reversed(self.children), recommendations):
search_item.text = recommendation
try:
search_item.url = wikipedia.page(recommendation).url
except:
search_item.url = 'https://en.wikipedia.org/wiki/' + recommendation.replace(" ", "_")
Builder.load_string('''
<SearchItem>:
font_size: self.height * 0.4
canvas.before:
Color:
rgba: [0.8, 0.8, 0.8, 1] if self.state == 'normal' else [30/255, 139/255, 195/255, 1]
Rectangle:
#pos: self.pos
#size: self.size[0], self.size[1]
size: root.size[0], root.size[1]
pos: self.pos[0], self.pos[1]
Color:
rgba: 0, 0, 0, 1
Line:
rectangle: self.x, self.y, self.width, self.height
color: 0, 0, 0, 1
<Urlpup>:
size_hint: 1, 1
auto_dismiss: False
title: 'Enter URL or keywords'
BoxLayout:
canvas.before:
Color:
rgba: 1, 1, 1, 1
Rectangle:
size: self.size
pos: self.pos
orientation: 'vertical'
padding: self.width * 0.1
spacing: self.height * 0.1
BoxLayout:
orientation: 'horizontal'
Spinner:
id: spinner
size_hint: 0.5, 0.4
pos_hint: { 'top' : 1 }
text: 'en'
values: 'en', 'fr', 'de', 'it'
SearchBar:
id: url_input
size_hint: 1, 0.4
pos_hint: { 'center_x' : 0.5, 'top' : 1 }
multiline: False
font_size: self.height*0.8
Button:
text: 'OK'
size_hint: 0.5, 0.4
pos_hint: { 'top' : 1 }
on_press: root.dismiss()
on_release: app.create_new_article(url_input.text)
Recommendations:
id: recommendations
orientation: 'vertical'
SearchItem
SearchItem
SearchItem
SearchItem
''')
class Urlpup(Popup):
pass
Here is a picture of the popup Window
You can see that the SearchItems are not tightly below the SearchBar, where I'm struggling to position it. Can you fix it? On the picture there are 4 SearchItems but I plan to have 10 Searchitems so it should work for 10 SearchItems or be flexible when I decide to change the number of SearchItems
The BoxLayout tries to distribute its available space equally among its children, unless you tell it otherwise. So, your top level BoxLayout in the Urlpup class evenly divides its space between the BoxLayout containing the TextInput and the Recommendations widget. You can reduce the space between those two widgets by limiting the space given to the horizontal BoxLayout. You can do that by specifying a height for that BoxLayout, like this:
BoxLayout:
size_hint_y: None
height: 50
orientation: 'horizontal'
Spinner:
id: spinner
size_hint: 0.5, 1
pos_hint: { 'top' : 1 }
text: 'en'
values: 'en', 'fr', 'de', 'it'
SearchBar:
id: url_input
size_hint: 1, 1
pos_hint: { 'center_x' : 0.5, 'top' : 1 }
multiline: False
font_size: self.height*0.8
Button:
text: 'OK'
size_hint: 0.5, 1
pos_hint: { 'top' : 1 }
on_press: root.dismiss()
on_release: app.create_new_article(url_input.text)
Note that the children of this BoxLayout all have a size_hint_y of 1.0, so they will all be the height that is specified for their container.
The Recommendations widget gets the remaining vertical space (minus the spacing). You can move the Recommendations even closer by reducing the spacing.

How can I add an MDDataTable to a Screen in a class that's not the main app class?

I'm trying to create an app where after you log in you're shown a table of users. If the login is successful I want the user to be sent to another screen that has an MDDataTable in it.
All of the examples I've found online only show how to display the table using the build method in the main app.
Code example:
class ScreenOne(Screen):
# Displays MDDataTable without the need to press anything to view the table.
class ScreenTwo(Screen):
# When this screen validates user successfully it sends
# me to the other screen that shows an
# MDDataTable and not a blank screen etc...
sm = ScreenManager()
class MainApp(MDApp):
def build(self):
sm.add_widget(ScreenOne(name='screenone'))
sm.add_widget(ScreenTwo(name='screentwo'))
return sm
MainApp().run()
I keep getting either a blank screen or the following error:
ValueError: KivyMD: App object must be initialized before loading root widget. See https://github.com/kivymd/KivyMD/wiki/Modules-Material-App#exceptions
I kept changing the code trying to fix it but I just kept running into all sorts of errors and problems and I just don't understand enough to make my question any clearer.
Here's my .py file code:
from kivy.properties import ObjectProperty
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.stacklayout import MDStackLayout
from kivymd.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import Screen, ScreenManager
from kivymd.uix.datatables import MDDataTable
from kivy.metrics import dp
from kivy.uix.anchorlayout import AnchorLayout
class ClientsTable(Screen):
def load_table(self):
layout = Screen()
data_tables = MDDataTable(
size_hint=(0.9, 0.6),
use_pagination=True,
check=True,
column_data=[
("No.", dp(30)),
("Column 1", dp(30)),
("Column 2", dp(30)),
("Column 3", dp(30)),
("Column 4", dp(30)),
("Column 5", dp(30)),],
row_data=[
(f"{i + 1}", "2.23", "3.65", "44.1", "0.45", "62.5")
for i in range(50)],)
layout.add_widget(data_tables)
return layout
class LoginPage(Screen):
username = ObjectProperty()
password = ObjectProperty()
def validate_user(self):
if self.username.text == "m":
sm.current = "Clientstable"
self.username.text = ""
self.password.text = ""
else:
print("Not here!")
sm = ScreenManager()
class MainWindow(MDApp):
def build(self):
self.title = "EasySport"
sm.add_widget(LoginPage(name='Loginpage'))
sm.add_widget(ClientsTable(name='Clientstable'))
return sm
if __name__ == "__main__":
MainWindow().run()
.kv code:
ScreenManager:
LoginPage:
ClientsTable:
<LoginPage>:
username: User
password: Pass
MDTextField:
id: User
hint_text: "Username"
size_hint: 0.5, 0.09
pos_hint: {"center_x": 0.5, "center_y": 0.7}
MDTextField:
id: Pass
hint_text: "Password"
size_hint: 0.5, 0.09
pos_hint: {"center_x": 0.5, "center_y": 0.6}
MDFillRoundFlatButton:
text: "Login"
size_hint: 0.5, 0.06
pos_hint: {"center_x": 0.5, "center_y": 0.4}
on_release:
root.validate_user()
root.manager.transition.direction = 'left'
<ClientsTable>:
MDFillRoundFlatButton:
text: "Back"
size_hint: 0.5, 0.06
pos_hint: {"center_x": 0.5, "center_y": 0.4}
on_release:
root.manager.transition.direction = 'right'
root.manager.current = 'Loginpage'
MDFillRoundFlatButton:
text: "Load Table"
size_hint: 0.5, 0.06
pos_hint: {"center_x": 0.5, "center_y": 0.3}
on_release:
root.load_table()
Now it doesn't do anything when I press the Load Table button!
I SOLVED IT!
Here's the .py file code:
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.stacklayout import MDStackLayout
from kivymd.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import Screen, ScreenManager
from kivymd.uix.datatables import MDDataTable
from kivy.metrics import dp
from kivy.uix.anchorlayout import AnchorLayout
class ClientsTable(Screen):
def load_table(self):
layout = AnchorLayout()
self.data_tables = MDDataTable(
pos_hint={'center_y': 0.5, 'center_x': 0.5},
size_hint=(0.9, 0.6),
use_pagination=True,
check=True,
column_data=[
("No.", dp(30)),
("Column 1", dp(30)),
("Column 2", dp(30)),
("Column 3", dp(30)),
("Column 4", dp(30)),
("Column 5", dp(30)),],
row_data=[
(f"{i + 1}", "2.23", "3.65", "44.1", "0.45", "62.5")
for i in range(50)],)
self.add_widget(self.data_tables)
return layout
def on_enter(self):
self.load_table()
class LoginPage(Screen):
username = ObjectProperty()
password = ObjectProperty()
def validate_user(self):
if self.username.text == "m":
sm.current = "Clientstable"
self.username.text = ""
self.password.text = ""
else:
print("Not here!")
sm = ScreenManager()
class MainWindow(MDApp):
def build(self):
sm.add_widget(LoginPage(name='Loginpage'))
sm.add_widget(ClientsTable(name='Clientstable'))
return sm
if __name__ == "__main__":
MainWindow().run()
And the .kv file code:
ScreenManager:
LoginPage:
ClientsTable:
<LoginPage>:
username: User
password: Pass
MDTextField:
id: User
hint_text: "Username"
size_hint: 0.5, 0.09
pos_hint: {"center_x": 0.5, "center_y": 0.7}
MDTextField:
id: Pass
hint_text: "Password"
size_hint: 0.5, 0.09
pos_hint: {"center_x": 0.5, "center_y": 0.6}
MDFillRoundFlatButton:
text: "Login"
size_hint: 0.5, 0.06
pos_hint: {"center_x": 0.5, "center_y": 0.4}
on_release:
root.validate_user()
I just needed to add the Screen's on_enter() method to my class like it says in the documentation. See here

Kivy: RecycleView not updating

I have two screens in my Kivy-based app, each with a RecycleView to display lists. Both RVs should update when I press a button (add_button_clicked()) on one screen. Currently, the first RV (AddRecipe screen) works mostly as intended. However, the RV on the ViewList screen does not update with new data.
I am new to Python and even newer to Kivy - what am I missing here?
.py:
#! python3
# GroceryList.py
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.spinner import Spinner
from kivy.properties import ListProperty
from kivy.properties import ObjectProperty
from kivy.uix.recycleview import RecycleView
selectedMeals = []
ingredients = []
class ViewList(Screen):
def updateList(self, portions, recipe):
ingredients.append((portions, recipe))
print(ingredients) # This proves updateList is getting called
##THE FOLLOW RV THINGS DON'T WORK:
self.ids.shoplist.data = [{'text': '%s (%s)' %(ingredients[i][0], ingredients[i][1])}
for i in range(len(ingredients))]
self.ids.shoplist.refresh_from_data()
##
class AddRecipe(Screen):
recipes = ListProperty()
recipes = {'Guacarole':5, 'Salsa':3, 'Chips':1} # Sample dict for demo
def add_one(self):
if self.addportions.text != '':
value = int(self.addportions.text)
self.addportions.text = str(value+1)
def subtract_one(self):
if self.addportions.text != '':
value = int(self.addportions.text)
self.addportions.text = str(value-1)
def add_button_clicked(self, recipe, portions):
if recipe != '':
selectedMeals.append((recipe, portions))
self.ids.mealslist.data = [{'text': '%s (%s)' %(selectedMeals[i][0], selectedMeals[i][1])}
for i in range(len(selectedMeals))]
self.ids.mealslist.refresh_from_data()
ViewList().updateList(portions, recipe)
def spinner_clicked(self, val):
self.addportions.text = str(self.recipes[val])
class WindowManager(ScreenManager):
pass
class GroceryList(App):
mealsRVdata = ListProperty()
shoppingRVdata = ListProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.sm = ScreenManager()
def build(self):
Builder.load_file("grocerylist.kv")
screens = [ViewList(name='viewlist'), AddRecipe(name='addrecipe')]
for screen in screens:
self.sm.add_widget(screen)
self.sm.current = "addrecipe"
return self.sm
if __name__ == '__main__':
GroceryList().run()
And .kv:
#:kivy 1.11.1
# GroceryList.kv
# Used by GroceryList.py
WindowManager:
AddRecipe:
ViewList:
<ViewList>:
name: "viewlist"
shoplist: shoplist
BoxLayout:
orientation: 'vertical'
BoxLayout:
size_hint: (1, 0.8)
RecycleView:
id: shoplist
data: app.shoppingRVdata
viewclass: 'RVLabel'
RecycleGridLayout:
cols: 1
size_hint: None, None
default_size: sp(200), sp(25)
height: self.minimum_height
width: self.minimum_width
BoxLayout:
size_hint: (1, 0.2)
Button:
text: "View shopping list"
on_release:
app.root.current = "viewlist"
Button:
text: "Add recipes"
on_release:
app.root.current = "addrecipe"
root.manager.transition.direction = "left"
<AddRecipe>:
name: "addrecipe"
addportions: addportions
mealslist: mealslist
BoxLayout:
orientation: 'vertical'
BoxLayout:
size_hint: (1, 0.08)
Label:
size_hint: (0.64, 1)
font_size: 20
text: "Select a meal to add"
Label:
size_hint: (0.36, 1)
font_size: 20
text: "Select portions"
BoxLayout:
size_hint: (1, 0.08)
Spinner:
id: add_spinner
size_hint: (0.64, 1)
text: ""
values: root.recipes.keys()
on_text:
root.spinner_clicked(add_spinner.text)
Button:
size_hint: (0.12, 1)
font_size: 36
text: "-"
on_release: root.subtract_one()
Label:
id: addportions
size_hint: (0.12, 1)
font_size: 24
text: ''
Button:
size_hint: (0.12, 1)
font_size: 36
text: "+"
on_release: root.add_one()
FloatLayout:
size_hint: (1, 0.08)
Button:
size_hint: (0.4, 1)
pos_hint: {"x": 0.3, "top": 1}
text: "Add to shopping list"
on_release:
root.add_button_clicked(add_spinner.text, addportions.text)
BoxLayout:
size_hint: (1, 0.68)
RecycleView:
id: mealslist
data: app.mealsRVdata
viewclass: 'RVLabel'
RecycleGridLayout:
cols: 1
size_hint: None, None
default_size: sp(200), sp(25)
height: self.minimum_height
width: self.minimum_width
BoxLayout:
size_hint: (1, 0.08)
Button:
text: "View shopping list"
on_release:
app.root.current = "viewlist"
root.manager.transition.direction = "right"
Button:
text: "Add recipes"
on_release:
app.root.current = "addrecipe"
<RVLabel#Label>:
text_size: self.size
I stumbled across the fix while browsing other questions - specify the RV more fully:
App.get_running_app().root.get_screen('viewlist').ids.shoplist.data = ....
I'm going to leave the question up, though, because now I want to know why this RV needs to be specified so completely, while the one in the AddRecipe screen doesn't.

Resources