
Security News
The Changelog Podcast: Practical Steps to Stay Safe on npm
Learn the essential steps every developer should take to stay secure on npm and reduce exposure to supply chain attacks.
Flet-ASP β The Flet Atomic State Pattern is a reactive state management library for Flet.
Flet ASP (Flet Atomic State Pattern) is a reactive state management library for Flet, bringing atom-based architecture and separation of concerns into Python apps β inspired by Flutter's Riverpod and ASP.
It provides predictable, testable, and declarative state through:
Atom β single reactive unit of stateSelector β derived/computed stateAction β handles async workflows like login, fetch, etc.Install using your package manager of choice:
# Pip
pip install flet-asp
# Poetry
poetry add flet-asp
# UV
uv add flet-asp
β
Reactive atoms - Automatic UI updates when state changes
β
Selectors - Derived/computed state (sync & async)
β
Actions - Async-safe workflows for API calls, auth, etc.
β
One-way & two-way binding - Seamless form input synchronization
β
Hybrid update strategy - Bindings work even before controls are mounted
β
Python 3.14+ optimizations - Free-threading, incremental GC, 3-5% faster
β
Lightweight - No dependencies beyond Flet
β
Type-safe - Full type hints support
The simplest way to use Flet-ASP: create an atom, bind it to a control, and update it.
import flet as ft
import flet_asp as fa
def main(page: ft.Page):
# Initialize state manager
fa.get_state_manager(page)
# Create a reactive atom
page.state.atom("count", 0)
# Create UI references
count_text = ft.Ref[ft.Text]()
def increment(e):
# Update the atom - UI updates automatically!
current = page.state.get("count")
page.state.set("count", current + 1)
# Build UI
page.add(
ft.Column([
ft.Text("Counter", size=30),
ft.Text(ref=count_text, size=50),
ft.ElevatedButton("Increment", on_click=increment)
])
)
# Bind atom to UI - the Text will update automatically
page.state.bind("count", count_text)
ft.app(target=main)
What's happening here?
atom("count", 0) - Creates a reactive piece of statebind("count", count_text) - Connects state to UIset("count", value) - Updates state β UI updates automatically!Perfect for input fields that need to sync with state.
import flet as ft
import flet_asp as fa
def main(page: ft.Page):
fa.get_state_manager(page)
# Create atoms for form fields
page.state.atom("email", "")
page.state.atom("password", "")
# UI references
email_field = ft.Ref[ft.TextField]()
password_field = ft.Ref[ft.TextField]()
message_text = ft.Ref[ft.Text]()
def login(e):
email = page.state.get("email")
password = page.state.get("password")
if email == "user@example.com" and password == "123":
message_text.current.value = f"Welcome, {email}!"
else:
message_text.current.value = "Invalid credentials"
page.update()
page.add(
ft.Column([
ft.Text("Login Form", size=24),
ft.TextField(ref=email_field, label="Email"),
ft.TextField(ref=password_field, label="Password", password=True),
ft.ElevatedButton("Login", on_click=login),
ft.Text(ref=message_text)
])
)
# Two-way binding: TextField β Atom
page.state.bind_two_way("email", email_field)
page.state.bind_two_way("password", password_field)
ft.app(target=main)
Key concept: bind_two_way() keeps the TextField and atom in perfect sync!
Derive new values from existing state automatically.
import flet as ft
import flet_asp as fa
def main(page: ft.Page):
fa.get_state_manager(page)
# Base atoms
page.state.atom("first_name", "John")
page.state.atom("last_name", "Doe")
# Computed state - automatically recalculates when dependencies change
@page.state.selector("full_name")
def compute_full_name(get):
return f"{get('first_name')} {get('last_name')}"
# UI
first_field = ft.Ref[ft.TextField]()
last_field = ft.Ref[ft.TextField]()
full_name_text = ft.Ref[ft.Text]()
page.add(
ft.Column([
ft.Text("Name Builder", size=24),
ft.TextField(ref=first_field, label="First Name"),
ft.TextField(ref=last_field, label="Last Name"),
ft.Divider(),
ft.Text("Full Name:", weight=ft.FontWeight.BOLD),
ft.Text(ref=full_name_text, size=20, color=ft.Colors.BLUE)
])
)
# Bind inputs
page.state.bind_two_way("first_name", first_field)
page.state.bind_two_way("last_name", last_field)
# Bind computed state
page.state.bind("full_name", full_name_text)
ft.app(target=main)
Magic! The full name updates automatically when first or last name changes.
Handle API calls, async operations, and side effects cleanly.
import asyncio
import flet as ft
import flet_asp as fa
def main(page: ft.Page):
fa.get_state_manager(page)
page.state.atom("user", None)
page.state.atom("loading", False)
# Define async action
async def login_action(get, set_value, params):
set_value("loading", True)
# Simulate API call
await asyncio.sleep(2)
# Validate credentials
email = params.get("email")
password = params.get("password")
if email == "test@test.com" and password == "123":
set_value("user", {"email": email, "name": "Test User"})
else:
set_value("user", None)
set_value("loading", False)
# Create action
login = fa.Action(login_action)
# UI
email_field = ft.Ref[ft.TextField]()
password_field = ft.Ref[ft.TextField]()
status_text = ft.Ref[ft.Text]()
async def handle_login(e):
await login.run_async(
page.state,
{
"email": email_field.current.value,
"password": password_field.current.value
}
)
user = page.state.get("user")
if user:
status_text.current.value = f"Welcome, {user['name']}!"
else:
status_text.current.value = "Login failed"
page.update()
# Listen to loading state
def on_loading_change(is_loading):
status_text.current.value = "Logging in..." if is_loading else ""
page.update()
page.state.listen("loading", on_loading_change)
page.add(
ft.Column([
ft.Text("Async Login", size=24),
ft.TextField(ref=email_field, label="Email"),
ft.TextField(ref=password_field, label="Password", password=True),
ft.ElevatedButton("Login", on_click=handle_login),
ft.Text(ref=status_text)
])
)
ft.app(target=main)
Actions encapsulate complex async logic in a testable, reusable way.
Create reusable components with encapsulated state.
import flet as ft
import flet_asp as fa
class Counter(ft.Column):
"""Reusable counter component with its own state."""
def __init__(self, page: ft.Page, counter_id: str, title: str):
super().__init__()
self.page = page
self.counter_id = counter_id
self.value_text = ft.Ref[ft.Text]()
# Initialize state for this counter
page.state.atom(f"{counter_id}_count", 0)
self.controls = [
ft.Container(
content=ft.Column([
ft.Text(title, size=20, weight=ft.FontWeight.BOLD),
ft.Text(ref=self.value_text, size=40, color=ft.Colors.BLUE),
ft.Row([
ft.IconButton(
icon=ft.Icons.REMOVE,
on_click=self.decrement
),
ft.IconButton(
icon=ft.Icons.ADD,
on_click=self.increment
)
], alignment=ft.MainAxisAlignment.CENTER)
], horizontal_alignment=ft.CrossAxisAlignment.CENTER),
padding=20,
border=ft.border.all(2, ft.Colors.BLUE),
border_radius=10
)
]
def did_mount(self):
# Bind when component is mounted
self.page.state.bind(f"{self.counter_id}_count", self.value_text)
def increment(self, e):
current = self.page.state.get(f"{self.counter_id}_count")
self.page.state.set(f"{self.counter_id}_count", current + 1)
def decrement(self, e):
current = self.page.state.get(f"{self.counter_id}_count")
self.page.state.set(f"{self.counter_id}_count", current - 1)
def main(page: ft.Page):
fa.get_state_manager(page)
page.add(
ft.Column([
ft.Text("Multiple Counters", size=30),
ft.Row([
Counter(page, "counter1", "Counter A"),
Counter(page, "counter2", "Counter B"),
Counter(page, "counter3", "Counter C")
])
])
)
ft.app(target=main)
State persists across navigation automatically!
import flet as ft
import flet_asp as fa
def home_screen(page: ft.Page):
"""Home screen with shared state."""
count_text = ft.Ref[ft.Text]()
def go_to_settings(e):
page.views.clear()
page.views.append(settings_screen(page))
page.update()
return ft.View(
"/",
[
ft.AppBar(title=ft.Text("Home"), bgcolor=ft.Colors.BLUE),
ft.Column([
ft.Text("Counter Value:", size=20),
ft.Text(ref=count_text, size=50, color=ft.Colors.BLUE),
ft.ElevatedButton("Go to Settings", on_click=go_to_settings)
])
]
)
def settings_screen(page: ft.Page):
"""Settings screen - modifies shared state."""
def increment(e):
current = page.state.get("count")
page.state.set("count", current + 1)
def go_back(e):
page.views.clear()
page.views.append(home_screen(page))
page.update()
return ft.View(
"/settings",
[
ft.AppBar(title=ft.Text("Settings"), bgcolor=ft.Colors.GREEN),
ft.Column([
ft.Text("Modify Counter", size=20),
ft.ElevatedButton("Increment", on_click=increment),
ft.ElevatedButton("Go Back", on_click=go_back)
])
]
)
def main(page: ft.Page):
fa.get_state_manager(page)
# Shared state across screens
page.state.atom("count", 0)
page.views.append(home_screen(page))
# Bind state after adding view (works with hybrid strategy!)
count_ref = page.views[0].controls[1].controls[1] # Get the count text
page.state.bind("count", ft.Ref[ft.Text]())
ft.app(target=main)
For advanced scenarios like testing, multi-window applications, or complex state architectures, you can create a StateManager outside the page scope.
import flet as ft
import flet_asp as fa
# Create global StateManager OUTSIDE the page
global_state = fa.StateManager()
def screen_a(page: ft.Page):
"""Main screen with counter."""
count_ref = ft.Ref[ft.Text]()
def increment(e):
# Use global_state instead of page.state
global_state.set("count", global_state.get("count") + 1)
def go_to_b(e):
page.go("/b")
view = ft.View(
"/",
[
ft.Text("Screen A - Global State", size=24, weight=ft.FontWeight.BOLD),
ft.Text(ref=count_ref, size=40, color=ft.Colors.BLUE_700),
ft.ElevatedButton("Increment", on_click=increment),
ft.ElevatedButton("Go to Screen B", on_click=go_to_b),
],
padding=20,
)
# Bind using global_state
global_state.bind("count", count_ref)
return view
def screen_b(page: ft.Page):
"""Secondary screen displaying the counter."""
def go_back(e):
page.go("/")
return ft.View(
"/b",
[
ft.Text("Screen B - Global State", size=24, weight=ft.FontWeight.BOLD),
ft.Text(f"Counter value: {global_state.get('count')}", size=16),
ft.Text("State is managed globally!", color=ft.Colors.GREEN_700),
ft.ElevatedButton("Go back", on_click=go_back),
],
padding=20,
)
def main(page: ft.Page):
"""App entry point."""
# IMPORTANT: Attach the page to the global StateManager
global_state.page = page
# Initialize atoms
global_state.atom("count", 0)
def route_change(e):
page.views.clear()
if page.route == "/b":
page.views.append(screen_b(page))
else:
page.views.append(screen_a(page))
page.update()
page.on_route_change = route_change
page.go("/")
ft.app(target=main)
When to use global state:
| Use Case | Why Global State? |
|---|---|
| Unit Testing | Test state logic without creating a Flet page |
| Multi-Window Apps | Share state between multiple page instances |
| Advanced Architectures | State exists independently of UI lifecycle |
| Framework Integration | Flet-ASP as part of a larger system |
Key differences:
| Aspect | page.state | global_state |
|---|---|---|
| Creation | fa.get_state_manager(page) | fa.StateManager() |
| Page binding | Automatic | Manual (global_state.page = page) |
| Scope | Inside main() | Global (module level) |
| Lifecycle | Managed by page | Manual |
| When to use | β Most cases | β οΈ Specific scenarios |
Common pitfalls:
# β WRONG - Forgot to attach page
global_state = fa.StateManager()
def main(page: ft.Page):
global_state.atom("count", 0) # Error: page not attached!
# β
CORRECT - Attach page first
global_state = fa.StateManager()
def main(page: ft.Page):
global_state.page = page # Attach first!
global_state.atom("count", 0)
Testing example:
import unittest
import flet_asp as fa
# Global state for testing
test_state = fa.StateManager()
class TestMyLogic(unittest.TestCase):
def setUp(self):
test_state._atoms.clear()
test_state.atom("count", 0)
def test_increment(self):
# Test logic without creating a Flet page
test_state.set("count", test_state.get("count") + 1)
self.assertEqual(test_state.get("count"), 1)
def test_computed_value(self):
test_state.atom("double", lambda: test_state.get("count") * 2)
test_state.set("count", 5)
self.assertEqual(test_state.get("double"), 10)
For a complete example, see 11.1_global_state_outside.py.
Fetch and compute data asynchronously.
import asyncio
import flet as ft
import flet_asp as fa
def main(page: ft.Page):
fa.get_state_manager(page)
# Base atoms
page.state.atom("user_id", 1)
# Async selector - fetches user data
@page.state.selector("user_data")
async def fetch_user(get):
user_id = get("user_id")
# Simulate API call
await asyncio.sleep(1)
# Mock user data
users = {
1: {"name": "Alice", "email": "alice@example.com"},
2: {"name": "Bob", "email": "bob@example.com"},
3: {"name": "Charlie", "email": "charlie@example.com"}
}
return users.get(user_id, {"name": "Unknown", "email": "N/A"})
# UI
user_info = ft.Ref[ft.Text]()
def update_user_info(user_data):
# Async selectors may return coroutines on first call, check the type
import inspect
if inspect.iscoroutine(user_data):
# Skip coroutine objects - they will be resolved automatically
return
if user_data:
user_info.current.value = f"{user_data['name']} ({user_data['email']})"
else:
user_info.current.value = "Loading..."
page.update()
def next_user(e):
current_id = page.state.get("user_id")
page.state.set("user_id", (current_id % 3) + 1)
# Listen to selector changes
page.state.listen("user_data", update_user_info)
page.add(
ft.Column([
ft.Text("User Profile", size=24),
ft.Text(ref=user_info, size=18),
ft.ElevatedButton("Next User", on_click=next_user)
])
)
ft.app(target=main)
Real-world e-commerce state management.
import flet as ft
import flet_asp as fa
def main(page: ft.Page):
fa.get_state_manager(page)
# State
page.state.atom("cart_items", [])
# Selectors
@page.state.selector("cart_total")
def calculate_total(get):
items = get("cart_items")
return sum(item["price"] * item["quantity"] for item in items)
@page.state.selector("cart_count")
def count_items(get):
items = get("cart_items")
return sum(item["quantity"] for item in items)
# Available products
products = [
{"id": 1, "name": "Laptop", "price": 999.99},
{"id": 2, "name": "Mouse", "price": 29.99},
{"id": 3, "name": "Keyboard", "price": 79.99}
]
# UI refs
cart_list = ft.Ref[ft.Column]()
cart_count_text = ft.Ref[ft.Text]()
cart_total_text = ft.Ref[ft.Text]()
def add_to_cart(product):
items = page.state.get("cart_items")
# Check if item already in cart
existing = next((item for item in items if item["id"] == product["id"]), None)
if existing:
existing["quantity"] += 1
else:
items.append({**product, "quantity": 1})
page.state.set("cart_items", items)
def render_cart():
items = page.state.get("cart_items")
cart_list.current.controls = [
ft.ListTile(
title=ft.Text(item["name"]),
subtitle=ft.Text(f"${item['price']:.2f} Γ {item['quantity']}"),
trailing=ft.Text(f"${item['price'] * item['quantity']:.2f}")
) for item in items
] if items else [ft.Text("Cart is empty")]
page.update()
# Listen to cart changes
page.state.listen("cart_items", lambda _: render_cart())
# Build UI
page.add(
ft.Row([
# Products column
ft.Column([
ft.Text("Products", size=24),
*[
ft.ElevatedButton(
f"{p['name']} - ${p['price']:.2f}",
on_click=lambda e, product=p: add_to_cart(product)
) for p in products
]
], expand=1),
# Cart column
ft.Column([
ft.Text("Shopping Cart", size=24),
ft.Text(ref=cart_count_text),
ft.Column(ref=cart_list),
ft.Divider(),
ft.Text(ref=cart_total_text, size=20, weight=ft.FontWeight.BOLD)
], expand=1)
])
)
# Bind computed values
page.state.bind("cart_count", cart_count_text, prop="value")
page.state.bind("cart_total", cart_total_text, prop="value")
render_cart()
ft.app(target=main)
Flet-ASP includes a hybrid update strategy that ensures bindings work reliably, even when controls are bound before being added to the page.
did_mount for custom controlspage.update() is called| Feature | Benefit | Performance Gain |
|---|---|---|
| Free-threading | Process bindings in parallel without GIL | Up to 4x faster for large apps |
| Incremental GC | Smaller garbage collection pauses | 10x smaller pauses (20ms β 2ms) |
| Tail call interpreter | Faster Python execution | 3-5% overall speedup |
Configuration (optional):
from flet_asp.atom import Atom
# For giant apps with 1000+ bindings on Python 3.14+
Atom.MAX_PARALLEL_BINDS = 8
# For small apps or to disable free-threading
Atom.ENABLE_FREE_THREADING = False
For more details, see PERFORMANCE.md.
Explore the examples/ folder for complete applications:
Basic Examples:
1.0_counter_atom.py - Simple counter1.1_counter_atom_using_state_alias.py - Counter with page.state2_counter_atom_bind_dynamic.py - Dynamic bindingIntermediate Examples:
3_computed_fullname.py - Computed state4_action_login.py - Async actions5_selector_user_email.py - Selectors6_listen_user_login.py - State listeners7_bind_two_way_textfield.py - Two-way binding8_session_reset_clear.py - State cleanupAdvanced Examples:
9_todo.py - Complete ToDo app10_cart_app.py - Shopping cart11_screen_a_navigation_screen_b.py - Navigation with page.state11.1_global_state_outside.py - Global state outside page scope12_python314_performance.py - Performance demo13_hybrid_binding_advanced.py - Hybrid bindingAtomic Design Examples:
14_atomic_design_dashboard/ - Complete dashboard with atoms, molecules, organisms, templates, and pages15_atomic_design_theming/ - Theme-aware component library with design tokens16_reactive_atomic_components/ - Reactive components with built-in state management
Flet-ASP is designed from the ground up to work seamlessly with the Atomic Design methodology - a powerful approach for building scalable, maintainable design systems.
Atomic Design is a methodology for creating design systems by breaking down interfaces into fundamental building blocks, inspired by chemistry:
π¬ Atoms β π§ͺ Molecules β 𧬠Organisms β π Templates β π± Pages
| Atomic Design Layer | Flet-ASP Feature | Example |
|---|---|---|
| Atoms | Reactive state values | page.state.atom("email", "") |
| Molecules | Computed state | @page.state.selector("full_name") |
| Organisms | Actions & workflows | Action(login_function) |
| Templates | State bindings | page.state.bind("count", ref) |
| Pages | Complete screens | Custom controls with encapsulated state |
We provide two comprehensive examples that demonstrate professional design system architecture:
A complete dashboard application showcasing the full Atomic Design hierarchy:
# Atoms define the foundation
from atoms import heading1, primary_button
# Molecules combine atoms
from molecules import stat_card
# Organisms compose molecules
from organisms import stats_grid
# Templates arrange organisms
from templates import dashboard_template
# Pages bring it all together
from pages import dashboard_page
Features:
An advanced example demonstrating design tokens and dynamic theming:
from theme_tokens import get_theme
from atoms import filled_button, text_field
from molecules import alert, stat_card
# All components automatically adapt to current theme
theme = get_theme()
button = filled_button("Submit") # Uses theme.colors.primary
Features:
Components that combine visual structure + reactive state in a single, reusable package:
from reactive_atoms import ReactiveCounter, ReactiveStatCard, ReactiveForm
# Create counter with built-in state!
counter = ReactiveCounter(page, "Counter A", initial_count=0)
page.add(counter.control)
# Interact via clean API
counter.increment() # +1
counter.decrement() # -1
counter.reset() # Set to 0
print(counter.value) # Get current value
# Stat card with auto-updates
users_card = ReactiveStatCard(
page,
title="Total Users",
atom_key="users",
initial_value="1,234",
icon_name=ft.Icons.PEOPLE,
show_trend=True
)
# Update programmatically
users_card.update_with_trend("2,500", "+15%") # β¨ UI updates automatically!
Features:
π― Consistency: Design tokens and atoms ensure uniform styling across your app
π Reusability: Build components once, use them everywhere with different state bindings
π Scalability: Add new features by combining existing atoms and molecules
π§ͺ Testability: Test atoms, molecules, and organisms in isolation
π€ Collaboration: Designers and developers work with the same component language
β‘ Reactivity: State changes propagate automatically through the component hierarchy
Join the community to contribute or get help:
If you like this project, please give it a GitHub star β
Contributions and feedback are welcome!
For feedback, open an issue with your suggestions.
Commit your work to the LORD, and your plans will succeed. Proverbs 16:3
FAQs
Flet-ASP β The Flet Atomic State Pattern is a reactive state management library for Flet.
We found that flet-asp demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Learn the essential steps every developer should take to stay secure on npm and reduce exposure to supply chain attacks.

Security News
Experts push back on new claims about AI-driven ransomware, warning that hype and sponsored research are distorting how the threat is understood.

Security News
Ruby's creator Matz assumes control of RubyGems and Bundler repositories while former maintainers agree to step back and transfer all rights to end the dispute.