Skip to main content

Async apps

Flet app can be written as an async app and use asyncio and other Python async libraries. Calling coroutines is naturally supported in Flet, so you don't need to wrap them to run synchronously.

By default, Flet uses threading library to run user sessions and execute event handlers in separate threads, but sometimes that could be an ineffective usage of CPU as it does nothing while waiting for a HTTP response or executing sleep().

Asyncio, on the other hand, allows implementing concurrency in a single thread by switching execution context between "coroutines". This is especially important for apps that are going to be published as static websites using Pyodide. Pyodide is a Python runtime built as a WebAssembly (WASM) and running in the browser. At the time of writing it doesn't support threading yet.

Getting started with async

To start with an async Flet app you should make main() method async:

import flet as ft

async def main(page: ft.Page):
await page.add_async(ft.Text("Hello, async world!"))

ft.app(main)

You can use await ft.app_async(main) if Flet app is part of a larger app and called from async code.

Notice the usage of await page.add_async(...) to add new controls to the page. In an async app you cannot use page.add() or other sync page methods anymore - you must use their async counterparts ending with _async everywhere in the code:

  • page.add()await page.add_async()
  • page.update()await page.update_async()
  • page.clean()await page.clean_async()
  • etc.

Control event handlers

Control event handlers could be both sync and async.

If a handler does not call any async methods it could be a regular sync method:

def page_resize(e):
print("New page size:", page.window_width, page.window_height)

page.on_resize = page_resize

However, if a handler calls async logic it must be async too:

async def main(page: ft.Page):
async def button_click(e):
await page.add_async(ft.Text("Hello!"))

await page.add_async(ft.ElevatedButton("Say hello!", on_click=button_click))


ft.app(main)

Async lambdas

There are no async lambdas in Python. It's perfectly fine to have a lambda event handler in async app for simple things:

page.on_error = lambda e: print("Page error:", e.data)

but you can't have an async lambda, so an async event handler must be used.

Sleeping

To delay code execution in async Flet app you should use asyncio.sleep() instead of time.sleep(), for example:

import asyncio
import flet as ft

async def main(page: ft.Page):
async def button_click(e):
await asyncio.sleep(1)
await page.add_async(ft.Text("Hello!"))

await page.add_async(
ft.ElevatedButton("Say hello with delay!", on_click=button_click)
)

ft.app(main)

Threading

Technically, nobody will stop you from using threading library in async app, but it would be a bad idea. asyncio versions of locks, queues and tasks, used by Flet API are not thread-safe and, for example, calling await page.update_async() from multiple threads will lead to unpredictable results. Also, threading library is not supported by Pyodide if you decide to deploy your app as a static website.

To run something in the background use asyncio.create_task(). For example, an async version of "countdown" control from User controls guide would be:

import asyncio
import flet as ft

class Countdown(ft.UserControl):
def __init__(self, seconds):
super().__init__()
self.seconds = seconds

async def did_mount_async(self):
self.running = True
asyncio.create_task(self.update_timer())

async def will_unmount_async(self):
self.running = False

async def update_timer(self):
while self.seconds and self.running:
mins, secs = divmod(self.seconds, 60)
self.countdown.value = "{:02d}:{:02d}".format(mins, secs)
await self.update_async()
await asyncio.sleep(1)
self.seconds -= 1

def build(self):
self.countdown = ft.Text()
return self.countdown

async def main(page: ft.Page):
await page.add_async(Countdown(120), Countdown(60))

ft.app(target=main)