TL;DR — FletX apps separate concerns into Pages (UI), Controllers (logic + state), and Services (reusable utilities). Data flows in one direction: user action → controller → state update → UI re-render.
FletX provides a modular, reactive architecture inspired by separation of concerns and dependency injection. It gives you a clear place for everything:
fromfletx.coreimportFletXPageimportfletasftclassAboutPage(FletXPage):defbuild(self):returnft.Column([ft.Text("About FletX",size=30,weight="bold"),ft.Text("FletX is a framework for building structured Flet apps."),])
Explanation: The controller holds reactive state (self.count). Methods like increment() modify it. FletX tracks reads of self.count, so any widget that depends on it will rebuild automatically.
fromfletx.coreimportFletXPageclassCounterPage(FletXPage):ctrl=CounterController()defbuild(self):returnft.Column([self._counter_text(),ft.Row([ft.ElevatedButton("-",on_click=lambda_:self.ctrl.decrement()),ft.ElevatedButton("+",on_click=lambda_:self.ctrl.increment()),])])@obxdef_counter_text(self):# Rebuilds when self.ctrl.count changesreturnft.Text(value=f"Count: {self.ctrl.count.value}",size=40,weight="bold")
Key insight: The @obx decorator wraps a builder function. When you read self.ctrl.count inside it, FletX remembers that dependency. When count changes, the builder runs again and the widget updates.
fromfletx.coreimportFletXController,RxStr,RxListfromfletx.decorators.reactiveimportreactive_debounce,reactive_memofromfletx.core.stateimportComputedclassSearchController(FletXController):def__init__(self):self.query=RxStr("")self.all_items=["Apple","Apricot","Avocado","Banana","Blueberry"]self.results=RxList([])super().__init__()@reactive_memo(maxsize=32)def_filter_items(self,q:str):# Pure computation: filter items matching q# FletX caches this automaticallyifnotq:returnself.all_itemsreturn[itemforiteminself.all_itemsifq.lower()initem.lower()]@reactive_debounce(0.3)defsearch(self,q:RxStr):# Executes 300ms after user stops typingself.results.value=self._filter_items(q.value)defclear_search(self):self.query.value=""self.results.value=[]
Explanation:
@reactive_memo caches expensive computations.
@reactive_debounce waits for the user to pause before searching.
The controller encapsulates all the logic; the page is just UI.
fromfletximportFletXfromfletx.coreimportFletXController,RxDict,RxBoolclassAppController(FletXController):"""Shared app-wide state"""def__init__(self):self.user=RxDict({})self.is_logged_in=RxBool(False)super().__init__()deflogin(self,email,password):# Validate and set userself.user.value={"email":email,"name":"John"}self.is_logged_in.value=Truedeflogout(self):self.user.value={}self.is_logged_in.value=False# Register a global instance of the controllerFletX.put(AppController,tag='app_ctrl')
fromfletximportFletXfromfletx.coreimportFletXPageclassProfilePage(FletXPage):# Get the global iinstance of AppController app_ctrl=FletX.find(AppController,tag='app_ctrl')defbuild(self):returnft.Column([self._profile_view(),ft.ElevatedButton("Logout",on_click=lambda_:self.app_ctrl.logout())])@obxdef_profile_view(self):ifnotself.app_ctrl.is_logged_in.value:returnft.Text("Not logged in")user=self.app_ctrl.user.valuereturnft.Text(f"Welcome, {user.get('name', 'Guest')}")
fromfletximportFletXclassSettingsPage(FletXPage):# Get the global instance of AppControllerapp_ctrl=FletX.put(AppController,tag='app_ctrl')defbuild(self):returnft.Column([ft.Text(f"Logged in as: {self.app_ctrl.user.value.get('email', 'N/A')}")])
Key: Both pages share the same AppController instance. When one page changes is_logged_in, all pages see the change.
fromfletx.coreimportFletXService,RxDict,RxBoolclassUserService(FletXService):"""Manages user data"""def__init__(self):super().__init__()self.user=RxDict({})self.is_loading=RxBool(False)deffetch_user(self,user_id:int):"""Simulate API call"""self.is_loading.value=Truetry:# In real code: response = requests.get(f"/api/users/{user_id}")self.user.value={"id":user_id,"name":"John Doe","email":"john@example.com"}finally:self.is_loading.value=False
fromfletx.coreimportFletXControllerclassProfileController(FletXController):def__init__(self):# Find the service from FletX's dependency injectionself.user_service=FletX.find(UserService)super().__init__()defload_profile(self,user_id:int):self.user_service.fetch_user(user_id)
fromfletx.coreimportFletXPagefromapp.controllers.home_controllerimportHomeControllerimportfletasftclassHomePage(FletXPage):ctrl=HomeController()defbuild(self):returnft.Column([ft.Text("Home",size=30,weight="bold"),ft.ElevatedButton("Go to Profile",on_click=lambda_:self.page.go("/profile/123"))])