flet-dev / flet

Flet enables developers to easily build realtime web, mobile and desktop apps in Python. No frontend experience required.
https://flet.dev
Apache License 2.0
11.5k stars 449 forks source link

Failure running Flet app from browser #4165

Closed jammerhund closed 1 month ago

jammerhund commented 1 month ago

Duplicate Check

Describe the bug

If i run the following code as a desktop app under Linux, everything works fine. If i run that for web, i get some errors. But only from a remote browser and not via 127.0.0.1. I get a small white message on red background in the browser: Unknown control: offstage

Code sample

Code ```python import flet as ft def index_page(page: ft.Page): content = ft.Column( [ ft.Text("Index page"), ft.ElevatedButton("Page 2", on_click=lambda _: page.go("/page2")) ] ) return content def page2(page: ft.Page): content = ft.Column( [ ft.Text("Page 2"), ft.ElevatedButton("Index Page", on_click=lambda _: page.go("/")) ] ) return content body = ft.Container() routes = { "/": index_page, "/page2": page2, } def route_change(route: ft.RouteChangeEvent): print(f'route_change(): {route}') body.content = routes[route.route](route.page) body.update() def main(page: ft.Page): page.on_route_change = route_change page.appbar = ft.AppBar( leading=ft.Icon(ft.icons.WAREHOUSE), leading_width=25, title=ft.Text("Footool"), center_title=False, bgcolor=ft.colors.SURFACE_VARIANT, actions=[ ft.IconButton(ft.icons.HOME), ft.IconButton(ft.icons.PIANO), ft.IconButton(ft.icons.PAGEVIEW) ] ) page.add(body) page.go("/") #ft.app(target=main) # works fine ft.app(target=main, port=8080, view=ft.AppView.WEB_BROWSER) # does not run from remote ```

To reproduce

  1. Run the code
  2. Get the IP or hostname from the maschine you running the code
  3. Browse to http://your_developement_host_or_ip:8080

Expected behavior

You can click the "Page 2" Button and you get the "Page 2". Then you click the "Index Page" Button and you get the "Index Page" and so on.

Screenshots / Videos

Captures [Upload media here]

Operating System

Linux

Operating system details

Linux Mint 21.3

Flet version

0.24.1

Regression

No, it isn't

Suggestions

The example code should run as desktop app and in any webbrowser from any remote host.

Logs

Logs ```console Future exception was never retrieved future: Traceback (most recent call last): File "/usr/lib/python3.10/concurrent/futures/thread.py", line 58, in run result = self.fn(*self.args, **self.kwargs) File "/home/dev/python/flet_web_problem/.venv/lib/python3.10/site-packages/flet_core/page.py", line 944, in wrapper handler(*args) File "/home/dev/python/flet_web_problem/main.py", line 34, in route_change body.update() File "/home/dev/python/flet_web_problem/.venv/lib/python3.10/site-packages/flet_core/control.py", line 310, in update self.__page.update(self) File "/home/dev/python/flet_web_problem/.venv/lib/python3.10/site-packages/flet_core/page.py", line 721, in update r = self.__update(*controls) File "/home/dev/python/flet_web_problem/.venv/lib/python3.10/site-packages/flet_core/page.py", line 835, in __update results = self.__conn.send_commands(self._session_id, commands).results File "/home/dev/python/flet_web_problem/.venv/lib/python3.10/site-packages/flet/fastapi/flet_app.py", line 410, in send_commands result, message = self._process_command(command) File "/home/dev/python/flet_web_problem/.venv/lib/python3.10/site-packages/flet_core/local_connection.py", line 93, in _process_command return self._process_remove_command(command.values) File "/home/dev/python/flet_web_problem/.venv/lib/python3.10/site-packages/flet/fastapi/flet_app.py", line 371, in _process_remove_command parent["c"].remove(id) ValueError: list.remove(x): x not in list ```

The value of id is first "_4".

Additional details

No response

OwenMcDonnell commented 1 month ago

If I follow a more idiomatic way of routing as outlined in the docs here, I don't run into this error. Here is the modified code.


import flet as ft

def index_page(page: ft.Page):
    content = ft.View(
        "/",
        [
            ft.Text("Index page"),
            ft.ElevatedButton("Page 2", on_click=lambda _: page.go("/page2"))
        ]
    )
    return content

def page2(page: ft.Page):
    content = ft.View(
        "/page2",
        [
            ft.Text("Page 2"),
            ft.ElevatedButton("Index Page", on_click=lambda _: page.go("/"))
        ]
    )
    return content

body = ft.Container()
routes = {
    "/": index_page,
    "/page2": page2,
}

def main(page: ft.Page):

    def route_change(route: ft.RouteChangeEvent):
        print(f'route_change(): {route}')
        page.views.clear()
        page.views.append(routes[route.route](route.page))
        page.update()

    page.on_route_change = route_change

    page.appbar = ft.AppBar(
        leading=ft.Icon(ft.icons.WAREHOUSE),
        leading_width=25,
        title=ft.Text("Footool"),
        center_title=False,
        bgcolor=ft.colors.SURFACE_VARIANT,
        actions=[
            ft.IconButton(ft.icons.HOME),
            ft.IconButton(ft.icons.PIANO),
            ft.IconButton(ft.icons.PAGEVIEW)
        ]
    )
    page.add(body)

    page.go("/")

#ft.app(target=main) # works fine
ft.app(target=main, port=8080, view=ft.AppView.WEB_BROWSER) # does not run from remote
OwenMcDonnell commented 1 month ago

Actually your approach (replacing container content on route events) can work as well but you'll need to create all the controls within the scope of the main function and not as globals. See below.


import flet as ft

def main(page: ft.Page):

    def index_page(page: ft.Page):
        content = ft.Column(
            [
                ft.Text("Index page"),
                ft.ElevatedButton("Page 2", on_click=lambda _: page.go("/page2"))
            ]
        )
        return content

    def page2(page: ft.Page):
        content = ft.Column(
            [
                ft.Text("Page 2"),
                ft.ElevatedButton("Index Page", on_click=lambda _: page.go("/"))
            ]
        )
        return content

    body = ft.Container()
    routes = {
        "/": index_page,
        "/page2": page2,
    }

    def route_change(route: ft.RouteChangeEvent):
        print(f'route_change(): {route}')
        body.content = routes[route.route](route.page)
        body.update()

    page.on_route_change = route_change

    page.appbar = ft.AppBar(
        leading=ft.Icon(ft.icons.WAREHOUSE),
        leading_width=25,
        title=ft.Text("Footool"),
        center_title=False,
        bgcolor=ft.colors.SURFACE_VARIANT,
        actions=[
            ft.IconButton(ft.icons.HOME),
            ft.IconButton(ft.icons.PIANO),
            ft.IconButton(ft.icons.PAGEVIEW)
        ]
    )
    page.add(body)

    page.go("/")

#ft.app(target=main) # works fine
ft.app(target=main, port=8080, view=ft.AppView.WEB_BROWSER) # does not run from remote
jammerhund commented 1 month ago

Hi, thank you for your response and code examples. Your are right. If i create the controls in the main function, everything is ok. But what is the reason for this behaviour? I mean, do I have to consider similar things when using Flet in the future? Is this an architectural problem or are there good reasons for it?

OwenMcDonnell commented 1 month ago

If those functions and variables are in the global scope then they are shared with any other instance of the app, like a new tab for example. Then when they are used in one instance they get out of sync with another instance. Avoid global definitions/variables, and you should be fine.

Closing this issue.