-
-
Notifications
You must be signed in to change notification settings - Fork 703
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A question about component update moment in the page lifecycle #50
Comments
It appears this problem affects continuously run tasks too. If they obtain a value after the page is loaded, but before the websocket is connected, then this value is not displayed in the UI too. And it won't be shown until it changes again - NiceGUI is smart enough to skip unnecessary websocket messages when the value is unchanged. |
Your question might be related to the breaking change in version 0.8. Before, we made use of the underlying JustPy framework to automatically update the UI after event handlers (if not actively prevented with a The following example shows the current date and time after a new client has connected. Although there's a delay of 1 second (and the page is updated and rendered), setting import asyncio
from datetime import datetime
from nicegui import ui
async def connect():
await asyncio.sleep(1.0)
label.text = datetime.now().isoformat()
with ui.page('/page', on_connect=connect):
label = ui.label()
ui.run() But probably you're aware of all that and the question is deeper: Is it possible that an update does not reach the client because the connection is not yet established? As far as I understand, the complete state of a page is held on the server (in Python world) for new clients. When they connect, they get the complete state of that page. I don't see how they could miss an update. In your example the tasks are even only started after a new connection is created. So how should this connection not be ready when the MTTQ response updates the UI? You write "but if the response comes between the page has been loaded, but before the websocket connection is up, then the component update is lost." - What do you mean with "loaded"? Doesn't the client "load" the page after establishing a websocket connection? |
There are actually two connections: HTML connection and websocket connection. The
In my understanding, So, as I see it, the process is as follows:
What would happen if the MQTT response that updates the UI arrives after 5., but before 6. is completed? Your example with current date and time is slightly different - your |
Oh, I see! Here is a reproduction: import asyncio
from nicegui import ui
async def on_connect():
label.text = 'loading...'
asyncio.create_task(takes_a_while())
async def takes_a_while():
await asyncio.sleep(0.1)
label.text = 'done'
with ui.page('/page', on_connect=on_connect):
label = ui.label()
ui.run() With a delay of 0.1s the text "done" never appears. It's the same when removing the delay completely. But a delay of 1.0s works on my machine. I have to think about if and how we could fix this problem. Maybe we can somehow keep track of UI updates on pages that are still connecting. These updates should be delivered once the socket is open. As a workaround I would add a delayed UI update to make sure there is an update after the socket connected. This might be a component update (e.g. async def takes_a_while():
await asyncio.sleep(0.1)
label.text = 'done'
await asyncio.sleep(1.0)
label.update() |
Or maybe just call |
Ok, in commit ea05fc6 I introduced an Using import asyncio
from nicegui import ui
async def on_page_ready():
label.text = 'loading...'
asyncio.create_task(takes_a_while())
async def takes_a_while():
await asyncio.sleep(0.1)
label.text = 'done'
with ui.page('/page', on_page_ready=on_page_ready):
label = ui.label()
ui.run() |
@falkoschindler I am trying to wrap my head around the design of nicegui but don't quite get it (like the questions in #72). Maybe you could point to some documentation of how things work out here? The example above assumes a global variable How are you supposed to change state of a component such as the label in your example above for any given larger UI? I tried to grab them from |
Maybe you could point to some documentation of how things work out here? You've probably seen nicegui.io. That's the only documentation at the moment. More detailed explanations can be found in GitHub issues and discussions. I probably should add some links in a Q&A section. How are you supposed to change state of a component such as the label in your example above for any given larger UI? A function like Where are the references/names actually "stored"? NiceGUI itself doesn't explicitly store UI elements, but only creates JustPy views. If you need the elements, you should hold them yourself as mentioned above. Usually you shouldn't need to tap into nicegui.globals like |
@hroemer thanks for your reply, the WifiButton demo really is great. Though I am still not getting a combined sample working, including sample_page.py:import asyncio
from nicegui import ui
class SamplePage(ui.page):
def __init__(self) -> None:
self.label = ui.label('Any label')
self.label.update()
row = ui.row()
ui.button('+', on_click=lambda: self.add_elements(row))
with ui.card():
with ui.row():
ui.label('Hello world!')
super().__init__(route="/page", title="repr page", on_page_ready=self.on_page_ready)
def add_elements(self, container):
with container:
result = 'something'
ui.label(result)
async def on_page_ready(self):
self.label.text = 'loading...'
asyncio.create_task(self.takes_a_while())
async def takes_a_while(self):
await asyncio.sleep(1.1)
self.label.text = 'done' main.py#!/usr/bin/env python3
from jpy.ng.sample_page import SamplePage
ui = SamplePage()
ui.run(reload=True,
host='127.0.0.1',
port=8000,
dark=True
) Accessing I would really like to hook up to the |
@hroemer Let me see...
Well well, NiceGUI definitely needs more documentation about building such custom elements or pages... |
@falkoschindler Thank you for the clarification, using the context managers is obviously the key here. The example is now working as expected. I still don't understand the (internal) use of the page and view stacks, though. The page is added to the stacks on def __enter__(self):
page_stack.append(self)
view_stack.append(self.view)
return self
def __exit__(self, *_):
page_stack.pop()
view_stack.pop() If I change the page route to root path Could you please explain how "pages" are intended to be used here or specifically how to "attach" or use the default Here's the current code for reference: sample_page.py:import asyncio
from nicegui import ui
class SamplePage(ui.page):
def __init__(self) -> None:
# call base initializer first:
super().__init__(route='/', title='sample page', on_page_ready=self.on_page_ready)
# now work within the `self` context:
with self:
self.label = ui.label('Any label')
# calling `update()` is not necessary
row = ui.row()
ui.button('+', on_click=lambda: self.add_elements(row))
with ui.card():
with ui.row():
ui.label('Hello world!')
def add_elements(self, container):
with container:
result = 'something'
ui.label(result)
async def on_page_ready(self):
self.label.text = 'loading...'
asyncio.create_task(self.takes_a_while())
async def takes_a_while(self):
await asyncio.sleep(1.1)
self.label.text = 'done' main.py:#!/usr/bin/env python3
from nicegui import ui
from sample_page import SamplePage as sample_page
sample_page()
ui.run(reload=True,
host='127.0.0.1',
port=8000,
dark=True
) |
They are used internally to keep track of which context is currently open and, thus, where to add an element. If you write with ui.card():
ui.label('A')
with ui.row():
ui.label('B')
ui.label('C')
ui.label('D') first a Changing the main page NiceGUI is intended to be easy to start with. So a three line hello world from nicegui import ui
ui.label('Hello world!')
ui.run() should simply work. This implies quite a lot (yes, we know, explicit is better than implicit...), e.g. how the server is started and that there is a main page at route "/". This page is automatically pushed to the Changing the main page is currently a bit tricky. You can get a reference from the first item on the from nicegui.globals import page_stack
main_page = page_stack[0]
main_page.dark = True
with main_page:
ui.label('Hi!') On the other hand it should also be ok to create a new page with route "/". Yes, then there are two routes with path "/", but since new pages insert their routes before others, it should work. with ui.page('/', dark=True):
ui.label('Hi!') It also might be useful to define a class MainPage(ui.page):
def __init__(self):
super().__init__('/', dark=True)
with self:
ui.label('Hi!')
MainPage() I'll think about whether NiceGUI should automatically remove routes that are overwritten by new pages. The implementation, however, could be challenging, since we would also need to replace the page in the |
I have a page with
on_connect
handler. In this handler, I start some tasks withasyncio.create_task
:Several tasks are ongoing and the last of them is single-shot. They all are async as they request and receive values from MQTT broker.
What I experience is that I receive the message from MQTT broker in the last task, but the corresponding UI elements are sometimes not updated.
I suspect this is due to the asynchronous nature of the task:
Please share your thoughts on the matter - am I right? Is it possible to check it somehow?
I currently fixed it by
await
ing the single-shot task instead of putting it to the common list of running tasks.Is this fix good or it's a hackish way to work around the problem?
The text was updated successfully, but these errors were encountered: