Reputation: 699
RecycleView isn't updating its table when data is changed from outside the RecycleView class. As a summary I'm trying to create a simple stock portfolio manager.
I have a custom RecycleView class which I call RecycleViewPortfolio that inherits from RecycleView. From my .kv file I have three buttons connected to functions populate_1, populate_2 and populate_3 within my RecycleViewPortfolio class. Whenever I press a button and call any of the populate functions, the RecycleView behaves as expected.
However whenever I change the RecycleView data from outside the RecycleViewPortfolio class the table doesn't update. For example I have setup a global variable which I have imported to both my .py file and .kv file. I would like to be able to update the table whenever this data in this global variable is changed.
I have tried looking at the documentation from Kivy which mentions different functions that are suppose to solve this issue. But I guess Im clueless to how to apply these methods.
import StackOverflow.globalvariables as GlobalVariables
from kivy.app import App
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview import RecycleView
from kivy.uix.recyclegridlayout import RecycleGridLayout
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.properties import BooleanProperty, ListProperty, ObjectProperty
class AddPopup(Popup):
"""Popup for adding asset"""
asset_name = ObjectProperty
asset_price = ObjectProperty
asset_amount = ObjectProperty
currency = ObjectProperty
asset_class = ObjectProperty
wrapped_button = ObjectProperty()
def __init__(self, *args, **kwargs):
super(AddPopup, self).__init__(*args, **kwargs)
def open(self, correct=True):
super(AddPopup, self).open(correct)
def save_asset(self):
# Make sure no input is empty
if self.asset_name.text.strip() and self.asset_price.text.strip()\
and self.asset_amount.text.strip() and self.currency.text.strip()\
and self.asset_class.text.strip():
GlobalVariables.rv_data_global = [{'text': self.asset_name.text.strip()},
{'text': self.asset_amount.text.strip()},
{'text': self.asset_price.text.strip()}]
self.dismiss()
class RecycleViewPortfolio(RecycleView):
def __init__(self, **kwargs):
super(RecycleViewPortfolio, self).__init__(**kwargs)
self.populate_2()
def populate_1(self):
root = App.get_running_app().root
root.add_popup.open(True)
self.data = GlobalVariables.rv_data_global
def populate_2(self):
self.data = [{'text': str(x)} for x in range(0, 6)]
def populate_3(self):
self.data = [{'text': str(x)} for x in range(6, 12)]
class PortfolioRoot(GridLayout):
"""root to all screens"""
add_popup = ObjectProperty(None)
list = ListProperty([])
def __init__(self, **kwargs):
super(PortfolioRoot, self).__init__(**kwargs)
self.add_popup = AddPopup()
def test_set_data(self):
GlobalVariables.rv_data_global = [{'text': str(x)} for x in range(12, 18)]
class SelectableRecycleGridLayout(FocusBehavior, LayoutSelectionBehavior, # View Behavior
RecycleGridLayout):
''' Adds selection and focus behaviour to the view. '''
class SelectableButton(RecycleDataViewBehavior, Button): # Data Behavior
''' Add selection support to the Label '''
index = None
selected = BooleanProperty(True)
selectable = BooleanProperty(True)
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
return super(SelectableButton, self).refresh_view_attrs(
rv, index, data)
def on_touch_down(self, touch):
''' Add selection on touch down '''
if super(SelectableButton, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos) and self.selectable:
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, rv, index, is_selected):
''' Respond to the selection of items in the view. '''
self.selected = is_selected
class PortfolioApp(App):
"""App object"""
def __init__(self, **kwargs):
super(PortfolioApp, self).__init__(**kwargs)
def build(self):
return PortfolioRoot()
PortfolioApp().run()
.kv file
#:kivy 1.10.0
#:import GlobalVariables StackOverflow.globalvariables
<SelectableButton>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (0, 0.517, 0.705, 1) if self.selected else (0, 0.517, 0.705, 1)
Rectangle:
pos: self.pos
size: self.size
background_color: [1, 0, 0, 1] if self.selected else [1, 1, 1, 1] # dark red else dark grey
on_release:
print("Pressed")
<WrappedLabel@Label>:
size_hint_y: None
height: self.texture_size[1] + (self.texture_size[1]/2)
markup: True
<RecycleViewPortfolio@RecycleView>:
viewclass: 'SelectableButton'
target_id: None
# id: rv_data_list
data: GlobalVariables.rv_data_global
SelectableRecycleGridLayout:
cols: 3
key_selection: 'selectable'
default_size: None, dp(26)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
multiselect: True
touch_multiselect: True
<PortfolioRoot>:
BoxLayout:
list: rv_data_list
size: root.size
orientation: 'vertical'
WrappedLabel:
text: "[b] Portfolio Manager [/b]"
font_size: min(root.height, root.width) / 10
GridLayout:
size_hint_y: None
height: root.height * 0.1
cols: 4
rows: 1
# Settings
padding: root.width * 0.001, root.height * 0.001
spacing: min(root.width, root.height) * 0.001
Button:
text: "Add"
background_color: [1, 1, 1, 1]
on_release:
rv_data_list.populate_1()
print("Add")
Button:
text: "Change"
background_color: [1, 1, 1, 1]
on_release:
rv_data_list.populate_2()
print("Change")
Button:
text: "Remove"
background_color: [1, 1, 1, 1]
on_release:
rv_data_list.populate_3()
print("Remove")
Button:
text: "Test"
background_color: [1, 1, 1, 1]
on_release:
root.test_set_data()
print("Test set data")
RecycleViewPortfolio:
id: rv_data_list
<AddPopup>:
size_hint: 0.8, 0.8
title: "Add Asset"
title_size: root.height * 0.05
auto_dismiss: False
asset_name: asset_name
asset_price: asset_price
asset_amount: asset_amount
currency: currency
asset_class:asset_class
wrapped_button: wrapped_button
BoxLayout:
orientation: 'vertical'
GridLayout:
rows: 5
cols: 2
padding: root.width * 0.02, root.height * 0.02
spacing: min(root.width, root.height) * 0.02
Label:
id: asset_name_label
text: "Asset name"
halign: "center"
font_size: root.height/25
text_size: self.width, None
center_y: .5
TextInput:
id: asset_name
text: "Asset name"
halign: "center"
font_size: root.height/25
text_size: self.width, None
center_y: .5
Label:
id: asset_price_label
text: "Asset price"
halign: "center"
font_size: root.height/25
text_size: self.width, None
center_y: .5
TextInput:
id: asset_price
text: "asset"
halign: "center"
font_size: root.height/25
text_size: self.width, None
center_y: .5
Label:
id: asset_amount_label
text: "Asset amount"
halign: "center"
font_size: root.height/25
text_size: self.width, None
center_y: .5
TextInput:
id: asset_amount
text: "Asset amount"
halign: "center"
font_size: root.height/25
text_size: self.width, None
center_y: .5
Label:
id: currency_label
text: "Asset currency"
halign: "center"
font_size: root.height/25
text_size: self.width, None
center_y: .5
TextInput:
id: currency
text: "currency"
halign: "center"
font_size: root.height/25
text_size: self.width, None
center_y: .5
Label:
id: asset_class_label
text: "Asset class"
halign: "center"
font_size: root.height/25
text_size: self.width, None
center_y: .5
TextInput:
id: asset_class
text: "Asset class"
halign: "center"
font_size: root.height/25
text_size: self.width, None
center_y: .5
Button:
id: wrapped_button
text: "Save"
size_hint: 1, None
height: root.height / 8
on_release: root.save_asset()
Button:
id: wrapped_button
text: "close"
size_hint: 1, None
height: root.height / 8
on_release: root.dismiss()
globalvariables.py
# global variables
rv_data_global = []
I expect to be able to create a popup window where I add information which is stored in a global variable and after changes are made I call for the RecycleView to be updated.
Edit: Added a working example
This example shows how Im able to use the buttons "Change" and "Remove" in order to populate the RecycleView as expected. However when the add button is pressed and the popup window appears and the save button is pressed the RecycleView doesn't update. If the add button is pressed again and directly closed the RecyleView gets updated and shows the correct information.
The same goes for the "Test" buttons where I call a function which changes the global variable. From there I have no idea of how to update the view since Im no longer working underneath the RecycleView class.
TLDR; Method for manually updating the RecycleView after data has been changed.
Upvotes: 0
Views: 3398
Reputation: 699
I found the answers to one of my questions. By adding:
self.ids.rv_data_list.data = GlobalVariables.rv_data_global
self.ids.rv_data_list.refresh_from_data()
to the test_set_data function, I am now able to refresh the data as I requested. Hence the magic was the refresh_from_data() method.
Through the App.get_running_app() I was able to access the refresh_from_data() command from the popup class.
root = App.get_running_app().root
root.ids.rv_data_list.data = GlobalVariables.rv_data_global
root.ids.rv_data_list.refresh_from_data()
I seem to have solved my own issues here. But if anyone has a better or cleaner solution, please let me know.
Upvotes: 3