Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 43b13fc

Browse files
committedMar 16, 2023
Rewrite startup screen to use event-driven updates instead of waiting in a loop.
1 parent 43d7aed commit 43b13fc

File tree

5 files changed

+104
-29
lines changed

5 files changed

+104
-29
lines changed
 

‎StellaPay.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from utils import Connections
3232
from utils.Screens import Screens
3333
from utils.SessionManager import SessionManager
34+
from utils.async_requests.AsyncResult import AsyncResult
3435

3536

3637
class StellaPay(MDApp):
@@ -195,26 +196,38 @@ def handle_user_data(user_data):
195196
# Store the user mapping so other screens can use it.
196197
self.user_mapping = user_data
197198

198-
screen_manager.get_screen(Screens.STARTUP_SCREEN.value).users_loaded = True
199+
screen_manager.get_screen(Screens.STARTUP_SCREEN.value).users_loaded = AsyncResult(True, data=True)
199200

200201
# Load user data
201202
self.data_controller.get_user_data(callback=handle_user_data)
202203

203204
# Callback for loaded product data
204205
def handle_product_data(product_data):
206+
207+
if product_data is None:
208+
Logger.error(f"StellaPayUI: Something went wrong when fetching product data!")
209+
screen_manager.get_screen(Screens.STARTUP_SCREEN.value).products_loaded = AsyncResult(False)
210+
return
211+
205212
Logger.info(f"StellaPayUI: Loaded {len(product_data)} products.")
206213

207214
# Signal to the startup screen that the products have been loaded.
208-
screen_manager.get_screen(Screens.STARTUP_SCREEN.value).products_loaded = True
215+
screen_manager.get_screen(Screens.STARTUP_SCREEN.value).products_loaded = AsyncResult(True, data=True)
209216

210217
self.loaded_all_users_and_products()
211218

212219
# Callback for loaded category data
213220
def handle_category_data(category_data):
221+
222+
if category_data is None:
223+
Logger.error(f"StellaPayUI: Something went wrong when fetching category data!")
224+
screen_manager.get_screen(Screens.STARTUP_SCREEN.value).categories_loaded = AsyncResult(False)
225+
return
226+
214227
Logger.info(f"StellaPayUI: Loaded {len(category_data)} categories.")
215228

216229
# Signal to the startup screen that the categories have been loaded.
217-
screen_manager.get_screen(Screens.STARTUP_SCREEN.value).categories_loaded = True
230+
screen_manager.get_screen(Screens.STARTUP_SCREEN.value).categories_loaded = AsyncResult(True, data=True)
218231

219232
self.data_controller.get_product_data(callback=handle_product_data)
220233

‎scrs/StartupScreen.py

+63-26
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,96 @@
1+
import sys
2+
13
from kivy import Logger
24
from kivy.app import App
35
from kivy.clock import Clock
46
from kivy.lang import Builder
7+
from kivy.properties import ObjectProperty
58
from kivy.uix.screenmanager import Screen
69

710
# Load KV file for this screen
811
from utils.Screens import Screens
12+
from utils.async_requests.AsyncResult import AsyncResult
913

1014
Builder.load_file('kvs/StartupScreen.kv')
1115

1216

1317
class StartupScreen(Screen):
18+
users_loaded: AsyncResult = ObjectProperty()
19+
categories_loaded: AsyncResult = ObjectProperty()
20+
products_loaded: AsyncResult = ObjectProperty()
21+
1422
def __init__(self, **kwargs):
1523
# call to user with arguments
1624
super(StartupScreen, self).__init__(**kwargs)
1725

18-
# Keep track of when data is loaded
19-
self.users_loaded = False
20-
self.categories_loaded = False
21-
self.products_loaded = False
22-
2326
# Calls upon entry of this screen
2427
#
2528
def on_enter(self, *args):
26-
self.ids.loading_text.text = "Waiting for data to load..."
27-
28-
App.get_running_app().loop.call_soon_threadsafe(self.wait_for_data_to_load)
29+
App.get_running_app().loop.call_soon_threadsafe(self.check_if_all_data_is_loaded)
2930

3031
# Called when all data has loaded
3132
def finished_loading(self, dt):
3233
self.manager.current = Screens.DEFAULT_SCREEN.value
3334

34-
# Called when we're waiting for the data to load
35-
def wait_for_data_to_load(self):
35+
def on_users_loaded(self, _, _2):
36+
self.check_if_all_data_is_loaded()
3637

37-
all_data_loaded = True
38+
def on_categories_loaded(self, _, _2):
39+
self.check_if_all_data_is_loaded()
3840

39-
if not self.users_loaded:
40-
all_data_loaded = False
41+
def on_products_loaded(self, _, _2):
42+
self.check_if_all_data_is_loaded()
43+
44+
def check_if_all_data_is_loaded(self) -> None:
45+
46+
self.set_loading_text("Waiting for data to load... (0/3)")
47+
48+
if self.users_loaded is None or self.users_loaded.result is None:
4149
Logger.debug("StellaPayUI: Waiting for users to load..")
50+
return
51+
elif self.users_loaded.received_result is False or self.users_loaded.result.data is False:
52+
self.set_loading_text("Failed to retrieve user data!")
53+
self.on_failed_to_load()
54+
return
55+
elif self.users_loaded.result.data is True:
56+
Logger.debug("StellaPayUI: Loaded user data!")
4257

43-
if not self.products_loaded:
44-
all_data_loaded = False
45-
Logger.debug("StellaPayUI: Waiting for products to load..")
58+
self.set_loading_text("Waiting for data to load... (1/3)")
4659

47-
if not self.categories_loaded:
48-
all_data_loaded = False
60+
if self.categories_loaded is None or self.categories_loaded.result is None:
4961
Logger.debug("StellaPayUI: Waiting for categories to load..")
62+
return
63+
elif self.categories_loaded.received_result is False or self.categories_loaded.result.data is False:
64+
self.set_loading_text("Failed to retrieve category data!")
65+
self.on_failed_to_load()
66+
return
67+
elif self.categories_loaded.result.data is True:
68+
Logger.debug("StellaPayUI: Loaded categories data!")
69+
70+
self.set_loading_text("Waiting for data to load... (2/3)")
71+
72+
if self.products_loaded is None or self.products_loaded.result is None:
73+
Logger.debug("StellaPayUI: Waiting for products to load..")
74+
return
75+
elif self.products_loaded.received_result is False or self.products_loaded.result.data is False:
76+
self.set_loading_text("Failed to retrieve products data!")
77+
self.on_failed_to_load()
78+
return
79+
elif self.products_loaded.result.data is True:
80+
Logger.debug("StellaPayUI: Loaded products data!")
81+
82+
self.set_loading_text("Data has loaded!")
83+
84+
self.set_loading_spinner(False)
85+
86+
Clock.schedule_once(self.finished_loading, 1)
87+
88+
def on_failed_to_load(self):
89+
self.set_loading_spinner(False)
90+
Clock.schedule_once(lambda: sys.exit(1), 3)
5091

51-
if all_data_loaded:
52-
# After everything has been loaded.
53-
self.ids.loading_text.text = "Data has loaded!"
92+
def set_loading_spinner(self, active: bool):
93+
self.ids.spinner_startup.active = active
5494

55-
# Done loading, so call callback in one second.
56-
Clock.schedule_once(self.finished_loading, 1)
57-
else:
58-
# Run the check again
59-
App.get_running_app().loop.call_later(0.5, self.wait_for_data_to_load)
95+
def set_loading_text(self, text: str):
96+
self.ids.loading_text.text = text

‎utils/async_requests/AsyncData.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from typing import Any
2+
3+
4+
class AsyncData:
5+
def __init__(self, data: Any):
6+
self.data = data

‎utils/async_requests/AsyncError.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class AsyncError:
2+
def __init__(self, message: str):
3+
self.message = message

‎utils/async_requests/AsyncResult.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from typing import Any, Union
2+
3+
from utils.async_requests.AsyncData import AsyncData
4+
from utils.async_requests.AsyncError import AsyncError
5+
6+
7+
class AsyncResult:
8+
"""
9+
An object of this class represents a result that is received after some time. The result will indicate whether a
10+
result was successfully obtained or not using the ``received_result`` member variable. If this variable is False,
11+
you can expect ``result`` to be of type `AsyncError`. Otherwise, it will be of type `AsyncData`.
12+
"""
13+
14+
def __init__(self, success: bool, data: Any = None, error_message: str = ""):
15+
self.received_result = success
16+
self.result: Union[AsyncData, AsyncError, None] = AsyncData(data) if success else AsyncError(error_message)

0 commit comments

Comments
 (0)