reflex-examples icon indicating copy to clipboard operation
reflex-examples copied to clipboard

Add collaborative counter example

Open milochen0418 opened this issue 2 years ago • 4 comments

https://github.com/pynecone-io/pynecone-examples/assets/12568287/135ecee5-b9d6-42a9-99d3-e0f52980aeb5

This counter demonstrates a simple Collaborative web app. And it can also support IoT applications too if you need.

  • Tick: Use the Tick pattern to update UI whenever the state is changing
  • State Manager: Use state manager to update every state for every client
  • API Routes: use API Routes to allow changing state from another place.

For this collaborative web app, how does MQTT Support? For example, you may use an MQTT subscriber to receive messages from IoT devices and send requests to the API route.

I show one pattern to implement a collaborative counter-example in this PR Someone may know how to improve it in a better way.

That's better to use squash merge because I have a messy repo. 😝

milochen0418 avatar Jun 03 '23 09:06 milochen0418

I try to improve the code in to real time without calling sleep by using await event.wait(), event.set(), event.clear() But the code cannot work because the on_click will not invoke the function directly.

But I can use localhost:8000/state_count/1122 to invoke the set_state_count function.

For more observation, If I click count up button 10 time, the count_up function will not be called. Then I can send request on localhost:8000/state_count/11 You can find out count become 11 first and there will be 10 time count up function happened. I don't know why, but it look like we cannot use await tickevent.wait() to optimized our code.

import pynecone as pc
import asyncio
from .styles import base_style

# create an instance of an event
tickevent = asyncio.Event()
tickevent.clear()

async def set_state_count(count_val:int):
    print("set_state_count with count_val = " + str(count_val))
    keys = list(app.state_manager.states) #convert the keys of dict into the list
    ret_count_val: int = count_val
    for token in keys: # change state.count for all current running frontend
        state = app.state_manager.get_state(token)
        state.count = count_val
        app.state_manager.set_state(token, state)
    print("start to call tickevent.set()")
    tickevent.set()
    print("end of call tickevent.set()")
    return {"state_count":ret_count_val}


class State(pc.State):
    count: int = 0
    is_run_tick:bool = False
    async def count_up(self):
        print("count_up start")
        self.count = self.count + 1
        await set_state_count(self.count)
        print("count_up end")
        

    async def count_down(self):
        print("count_up start")
        self.count = self.count - 1
        await set_state_count(self.count)
        print("count_down end")
        
    async def tick(self):
        print("tick")
        # run tick for update frontend ui for updating count
        if self.is_run_tick:
            # wait for the event to be set
            await tickevent.wait()
            tickevent.clear()
            return self.tick
        
    async def onload(self):
        self.is_run_tick = True
        return self.tick
    
        
def index():
    return pc.center(
        pc.vstack(
            pc.heading("Counter"),
            pc.text("Collaborative Count"),
            pc.hstack(
                pc.button("+", on_click=State.count_up),
                pc.button("-", on_click=State.count_down),
            ),
            pc.link(State.count),
            spacing="1.5em",
            font_size="2em",
        ),
        padding_top="10%",
    )

print("Hint:You can open http://localhost:8000/state_count/33 to set count value as 33")
# Add state and page to the app.

app = pc.App(state=State, style=base_style)
app.api.add_api_route("/state_count/{count_val}", set_state_count)
app.add_page(
    index,
    title = "Collaborative Counter App",
    description = "A collaborative counter app",
    meta = [
        {"name": "theme_color", "content": "#FFFFFF"},
        {"char_set": "UTF-8"},
        {"property": "og:url", "content": "url"},
    ],
    on_load = State.onload,
)
app.compile()

milochen0418 avatar Jun 05 '23 11:06 milochen0418

If you still want this example merged, you can upgrade it to use reflex==0.2.0, then we'll review it.

Lendemor avatar Jul 01 '23 13:07 Lendemor

If you still want this example merged, you can upgrade it to use reflex==0.2.0, then we'll review it. Thanks,

I have updated it to support reflex 0.2.0 now. We can start to review it.

milochen0418 avatar Jul 02 '23 14:07 milochen0418

need fix

Initial value when opening a new tab does not synchronize to other already existing tabs.

improvement

I think it is working for now, but we should consider adding a built in way to synchronize some variable between many states (or use database Model to keep synchronization working)

@Lendemor Thanks you so much ! Thanks for your nice testing and the nice suggestion.

milochen0418 avatar Jul 06 '23 17:07 milochen0418