Flet-Easy v0.3.0 Feature Showcase¶
Welcome to the Flet-Easy v0.3.0 showcase! This document provides real, runnable examples directly from our test suite, demonstrating the new declarative components, Flet hooks, and powerful class-based middlewares.
Example 1: Declarative Routing with Decorators¶
This example (tests/render-page-decorator.py) demonstrates how to build a modern application using Flet's @ft.component ecosystem integrated seamlessly with Flet-Easy's @app.page() decorators.
Flet Version Requirement
The use of declarative components (@ft.component) is only available from Flet version 0.80.0 onwards.
1. State Management and Reusable Components¶
We start by defining a reactive state using @ft.observable and a dataclass. Then, we create a reusable Flet component (counter) that manages its own internal state using ft.use_state(). Notice that this component does not need a route decorator!
import asyncio
from dataclasses import dataclass
import flet as ft
import flet_easy as fs
app = fs.FletEasy()
@dataclass
@ft.observable
class CounterState:
count: int = 0
def add(self):
self.count += 1
def reset(self):
self.count = 0
def remove(self):
self.count -= 1
# Reusable UI component
@ft.component
def counter():
state, _ = ft.use_state(CounterState())
return ft.Column(
controls=[
ft.Text(value=f"{state.count}", size=30),
ft.Row(
controls=[
ft.Button("Add", on_click=state.add),
ft.Button("Remove", on_click=state.remove),
ft.Button("Reset", on_click=state.reset),
],
alignment="center",
),
],
alignment="center",
horizontal_alignment="center",
)
2. Class-Based Middlewares¶
Flet-Easy v0.3.0 introduces fs.MiddlewareRequest. This allows you to define before_request and after_request hooks.
Execution Hierarchy (Outside-In)
Notice the power of the new middleware hierarchy! Middlewares execute in a strict outer-to-inner cascading order: Global ➔ Sub-Router ➔ Page-Specific. You can even pass arrays of mixed functional and class-based middlewares (e.g., middleware=[funcMd, ClassMd]).
Here, we add a logging middleware globally to all routes, and a smaller functional middleware specifically for our home page.
class MiddlewareRoutes(fs.MiddlewareRequest):
def before_request(self):
print(f"route: {self.data.route} - before home")
def after_request(self):
print(f"route: {self.data.route} - after home")
# Add middleware to all routes globally
app.add_middleware(MiddlewareRoutes)
# Functional middleware for specific pages
def middleware_home(data: fs.Datasy):
data.page.show_dialog(ft.SnackBar(ft.Text(f"route: {data.route} - Hello from middleware!")))
3. Declarative Page Routing¶
By combining @app.page() and Flet's @ft.component, Flet-Easy natively passes its context directly into declarative components. Notice how you can use native navigation (ft.context.page.push_route) right alongside traditional data.go() calls!
@ft.component
# Array of middlewares in specific page decorator!
@app.page(route="/", title="Home", middleware=[middleware_home], cache=True)
def home_page(data: fs.Datasy):
async def go_test():
await ft.context.page.push_route("/test")
return ft.View(
controls=[
counter(), # Integrating our stateful component
ft.Button("go test", on_click=go_test),
ft.Button("go progress-bar", on_click=data.go("/add-pagesy/progress-bar")),
],
vertical_alignment="center",
horizontal_alignment="center",
)
@app.page(route="/test", title="test")
def test(data: fs.Datasy):
return ft.View(
controls=[
ft.Text("test"),
ft.Button("go back", on_click=data.go("/")),
ft.Button("go test3", on_click=data.go("/add-pagesy/progress-bar")),
],
vertical_alignment="center",
horizontal_alignment="center",
)
4. Page Groups and Sub-Routes (AddPagesy)¶
To organize large applications natively, you can group routes using fs.AddPagesy(). In v0.3.0, AddPagesy now fully supports assigning both Functional and Class-Based Middlewares directly!
Sub-Router Middleware Execution
When you assign middleware=[func, ClassMd] to an AddPagesy instance, they act as Sub-Router Middlewares. They execute after any Global middlewares, but before your specific Page middlewares.
Here we create a sub-module with a /add-pagesy prefix, demonstrating how smoothly asyncio and ft.use_state work inside Flet-Easy pages.
@dataclass
@ft.observable
class AppState:
counter: float
async def start_counter(self):
self.counter = 0
for _ in range(0, 10):
self.counter += 0.1
await asyncio.sleep(0.5)
# Group routes under a specific prefix
app2 = fs.AddPagesy(
route_prefix="/add-pagesy",
middleware=[middleware_home, MiddlewareRoutes], # Array of functional and class-based middleware!
)
@ft.component
@app2.page(route="/progress-bar", title="progress-bar")
def progress_bar(data: fs.Datasy):
state, _ = ft.use_state(AppState(counter=0))
async def go_back():
await ft.context.page.push_route("/")
return ft.View(
controls=[
ft.ProgressBar(state.counter),
ft.Button("Run!", on_click=state.start_counter),
ft.Button("go back", on_click=go_back),
],
vertical_alignment="center",
horizontal_alignment="center",
)
# Register the sub-routes and run
app.add_pages([app2])
app.run()
Demo¶
Example 2: Manual Routing Without Decorators¶
This example (tests/render-page.py) showcases Flet-Easy's impressive ability to handle complex class-based architectures, and how to register them manually using fs.Pagesy (bypassing the @app.page() decorators entirely).
1. View Functions and Declarative Views¶
Flet-Easy seamlessly bridges URL parameters and the data object into declarative components. For instance:
home: An imperative function needingdata: fs.Datasy.about: A declarative component.data: fs.Datasyis injected optionally if requested!
import flet as ft
import flet_easy as fs
# 1. Standard imperative view function
def home(data: fs.Datasy):
return ft.View(
controls=[ft.Text("Home"), ft.Button("go about", on_click=data.go("/about"))],
vertical_alignment="center",
horizontal_alignment="center",
)
# 2. Declarative component (@ft.component)
@ft.component
def about(data: fs.Datasy):
async def go_contact(e):
await ft.context.page.push_route("/contact")
return ft.View(
controls=[
ft.Text("About"),
ft.Button("go contact", on_click=go_contact),
],
vertical_alignment="center",
horizontal_alignment="center",
)
# 3. Declarative component using data.go()
@ft.component
def contact(data: fs.Datasy):
return ft.View(
controls=[
ft.Text("Contact"),
ft.Button("go config", on_click=data.go("/config")),
],
vertical_alignment="center",
horizontal_alignment="center",
)
# 4. Standard imperative view function without decorator
def config(data: fs.Datasy):
return ft.View(
controls=[
ft.Text("Config"),
ft.Button("go support", on_click=data.go("/support")),
],
vertical_alignment="center",
horizontal_alignment="center",
)
2. Class-Based Views¶
Flet-Easy v0.3.0 brings huge enhancements to OOP architectures!
Support: A classic imperative class. Flet-Easy passesdataautomatically into__init__.Profile: A declarative class with@ft.componentwrapping thebuild()method. Flet-Easy instantly injectsself.datawithout you needing to explicitly declare an__init__()method!
# 5. Class-based imperative view.
class Support:
def __init__(self, data: fs.Datasy):
self.data = data
def build(self):
return ft.View(
controls=[
ft.Text("Support"),
ft.Button("go profile", on_click=self.data.go("/profile")),
],
vertical_alignment="center",
horizontal_alignment="center",
)
# 6. Class-based declarative component (v0.3.0+ Feature).
class Profile:
@ft.component
def build(self):
return ft.View(
controls=[
ft.Text("Profile"),
ft.Button("go home", on_click=self.data.go("/")),
],
vertical_alignment="center",
horizontal_alignment="center",
)
3. Registering Pages Manually¶
Rather than decorating each function, we can instantiate the app, define our middlewares, and explicitly map all of our views through fs.Pagesy().
Page-Specific Overrides
Even if you have Global or Sub-Router middlewares protecting a route, you can attach a dedicated middleware=[...] directly to a specific Pagesy() instance for granular execution control.
# Middleware 1: Functional Middleware
def middleware_home(data: fs.Datasy):
print(f"before page (function) - route: {data.route} - Hello from middleware!")
# Middleware 2: Class-based Middleware
class MiddlewareDialog(fs.MiddlewareRequest):
def before_request(self):
print(f"before page (class) - route: {self.data.route} - Hello from middleware!\n")
def after_request(self):
self.data.page.show_dialog(
ft.SnackBar(
ft.Text(f"after page (class) - route: {self.data.route} - Hello from middleware!")
)
)
# 1. Create app
app = fs.FletEasy()
# 2. Add routes using Pagesy
app.add_routes(
add_views=[
# Page-specific middleware override!
fs.Pagesy(route="/", title="Home", view=home, middleware=[middleware_home, MiddlewareDialog]),
fs.Pagesy(route="/about", title="About", view=about),
fs.Pagesy(route="/contact", title="Contact", view=contact),
fs.Pagesy(route="/config", title="Config", view=config),
fs.Pagesy(route="/support", title="Support", view=Support),
fs.Pagesy(route="/profile", title="Profile", view=Profile),
]
)
# 3. Add middlewares and run
app.add_middleware(middleware_home, MiddlewareDialog)
app.run()
Demo¶
Example 3: Essential Application Scenarios¶
While the test scripts demonstrate powerful declarative and imperative structures, Flet-Easy v0.3.0 brings massive upgrades for State, Navigation, and Authentication. Here are specific snippets mirroring the CHANGELOG.md features not fully present in the basic component tests.
1. Navigation Bar with index Routing¶
More details in Navigation Bar
The NavigationBar integration is now fully native. Flet-Easy automatically computes tab selections when you use on_change=data.go_navigation_bar and define index values in your @app.page() decorators.
Also note data.page_reload() and data.logout(). These v0.3.0 methods execute immediately; no lambda wrapping required inside normal execution flows or event bindings!
@app.view
def global_view(data: fs.Datasy, page: ft.Page):
return fs.Viewsy(
appbar=ft.AppBar(
title=ft.Text("Flet-Easy v0.3.0"),
actions=[
ft.IconButton(ft.Icons.REFRESH, on_click=lambda _: data.page_reload()),
ft.IconButton(ft.Icons.LOGOUT, on_click=lambda _: data.logout("auth_token", next_route="/login")),
]
),
navigation_bar=ft.NavigationBar(
destinations=[
ft.NavigationBarDestination(icon=ft.Icons.HOME, label="Home"), # mapped to index 0
ft.NavigationBarDestination(icon=ft.Icons.PERSON, label="Profile"), # mapped to index 1
],
on_change=data.go_navigation_bar, # 🆕 Native auto-routing!
)
)
@app.page("/home", index=0)
def home_page(data: fs.Datasy):
return ft.View([ft.Text("I am tab 0!")])
2. Form Caching and Dynamic Updates¶
More details in Form Caching
Imperative functions support cache=True. State changes (like filling forms or incrementing counters) will persist perfectly when you navigate away and come back.
To update cached controls in the background (like live clocks) without disrupting user state, use data.dynamic_control():
@app.page("/cached-form", cache=True)
def form_page(data: fs.Datasy):
# This value persists even if you switch tabs!
form_input = ft.TextField(label="Start typing, move to another page and come back")
# 🆕 dynamic_control runs every time the cached page is visited
visitors_text = ft.Text("")
def update_visitors():
visitors_text.value = f"Visited at {datetime.now().time()}"
data.dynamic_control(visitors_text, update_visitors)
return ft.View([form_input, visitors_text])
Note: As mentioned in the CHANGELOG, the
cache=Truemechanism currently only works in standard imperative routing. Declarative routing (@ft.componentclasses) does not yet support automatic caching, so persist form data safely intoself.data.page.shared_preferencesinstead.
3. JWT Authentication¶
More details in JWT Authentication
The SecretKey and data.login() methods power secure encryption naturally. Pass your authentication payloads into data.login() or data.login_async(), and seamlessly verify identity internally using decode_jwt() (or decode_jwt_async()). Note that the next_route parameter in the login methods is now mandatory for secure and immediate redirections.
app = fs.FletEasy(
route_init="/dashboard",
route_login="/login",
secret_key=fs.SecretKey(secret="your-secret-key"),
logger=True # 🆕 Advanced terminal logging
)
@app.page("/login")
def login(data: fs.Datasy):
async def handle_login(_):
# Authenticate asynchronously, store encrypted token, and jump to dashboard
await data.login_async("auth_token", {"user": "admin", "role": "superuser"}, next_route="/dashboard")
return ft.View([ft.ElevatedButton("Log In", on_click=handle_login)])
@app.login
async def login_required(data: fs.Datasy) -> bool:
# 🆕 decode_jwt_async is highly recommended over fs.decode_async
return await data.decode_jwt_async(key_login="auth_token")
4. Seamless Pop Configurations¶
When a user triggers a back navigation via the system back button or page.views.pop(), you can instantly synchronize Flet-Easy's internal routing engine by passing the event natively into data.confirm_pop().
@app.view
def global_view(data: fs.Datasy, page: ft.Page):
# Flet-Easy auto-aligns history state seamlessly
page.on_view_pop = data.confirm_pop
return fs.Viewsy(
appbar=ft.AppBar(title=ft.Text("Flet-Easy v0.3.0"))
)
5. Direct Execution vs Lambdas¶
More details in Direct Execution vs Lambdas
Flet-Easy v0.3.0 cleanly distinguishes between delayed event triggers and instant executions:
data.go(route): Returns an asynchronous lambda. Perfect for event bindings likeon_click=data.go("/route").data.go_route(route): Executes immediately. Use this inside custom functions when you need to trigger a route change manually.data.go_back(): Executes immediately to return to the previous path in the history.data.redirect(route): Executes immediately. Typically used within middlewares or decorators to enforce an instant redirect before a page even loads.
6. Package Architecture & Modernization¶
Under the hood, v0.3.0 is completely rebuilt for speed, scale, and modern Python:
- Package Reorganization: The monolithic engine has been cleanly split into
core/,security/, andui/, whilst maintaining 100% backward compatibility with your existingimport flet_easyimports. - Python 3.9+ (PEP 585): Full standardization of built-in generics (
list,dict), bringing better IDE hints, zero deprecation warnings, and a leaner router logic. - Ultimate Flet Compatibility: Features robust routing fallbacks designed to safely support legacy Flet
0.27.*all the way through the@ft.componentarchitecture of Flet0.80.*.