TL;DR: A FletXPage is a complete screen component with structured lifecycle (on_init(), on_destroy()), a build() method for UI, automatic controller management, event handling (keyboard, gestures, window events), and built-in support for app bars, drawers, floating action buttons, and keyboard shortcuts.
A FletXPage represents a single screen or view in your FletX application. It's much more than Flet's basic page - it's a fully-featured component system that includes:
Lifecycle management - Structured stages from initialization to disposal
Built-in event handling - Keyboard, gestures, window resize, media changes
Navigation elements - App bars, drawers, FABs, bottom sheets
Controller integration - Seamless connection to business logic
Reactive updates - Automatic UI refresh on state changes
Keyboard shortcuts - Power-user features
Performance monitoring - Track render times and statistics
Resource cleanup - Automatic disposal of effects and subscriptions
importfletasftdefmain(page:ft.Page):page.title="Todo"todos=[]# State scattereddefadd_todo(e):todos.append(input_field.value)# Manual UI updatelist_view.controls.clear()fortodointodos:list_view.controls.append(ft.Text(todo))page.update()input_field=ft.TextField()list_view=ft.Column()page.add(input_field,ft.ElevatedButton("Add",on_click=add_todo),list_view)# No cleanup, no structureft.app(target=main)
fromfletx.coreimportFletXPage,FletXControllerfromfletx.decoratorsimportobximportfletasftclassTodoController(FletXController):def__init__(self):super().__init__()self.todos=self.create_rx_list([])defadd_todo(self,title:str):self.todos.append(title)self.emit_local("todo_added",title)classTodoPage(FletXPage):def__init__(self):super().__init__()self.controller=TodoController()defon_init(self):# Called when page appearsself.controller.on_local("todo_added",self._on_todo_added)def_on_todo_added(self,event):self.refresh()defbuild(self):returnft.Column([ft.TextField(label="Add todo"),ft.Column([ft.Text(todo)fortodoinself.controller.todos])])
classMyPage(FletXPage):defon_init(self):"""Called when page becomes visible"""print("Page appeared!")# Load dataself.controller.load_data()# Setup listenersself.controller.on_local("data_loaded",self._on_data_ready)defon_destroy(self):"""Called when page will be removed"""print("Page will disappear!")# Cancel requestsself.controller.cancel_pending()# Close connectionsifhasattr(self,'websocket'):self.websocket.close()
fromfletx.coreimportFletXControllerclassUserController(FletXController):def__init__(self):super().__init__()# Create reactive variables using create_rx_* methodsself.username=self.create_rx_str("")self.email=self.create_rx_str("")self.is_loading=self.create_rx_bool(False)self.users=self.create_rx_list([])self.error=self.create_rx_str("")defload_users(self):"""Load users from API"""self.set_loading(True)self.clear_error()try:# Fetch datausers_data=self._fetch_users()self.users.value=users_data# Emit eventself.emit_local("users_loaded",len(users_data))exceptExceptionase:self.set_error(str(e))self.emit_local("error",str(e))finally:self.set_loading(False)defadd_user(self,username:str,email:str):"""Add a new user"""user={"name":username,"email":email}self.users.append(user)self.emit_local("user_added",user)def_fetch_users(self):# Simulate API callreturn[{"name":"Alice","email":"alice@example.com"},{"name":"Bob","email":"bob@example.com"}]
classUsersPage(FletXPage):def__init__(self):super().__init__()# Create controllerself.controller=UserController()defon_init(self):"""When page appears"""# Load dataself.controller.load_users()# Listen to eventsself.controller.on_local("users_loaded",self._on_users_loaded)self.controller.on_local("user_added",self._on_user_added)self.controller.on_local("error",self._on_error)def_on_users_loaded(self,event):print(f"Loaded {event.data} users")self.refresh()def_on_user_added(self,event):print(f"Added user: {event.data}")self.refresh()def_on_error(self,event):print(f"Error: {event.data}")self.refresh()@obxdefbuild(self):# Show loader while loadingifself.controller.is_loading:returnft.Center(content=ft.Column([ft.ProgressRing(),ft.Text("Loading...")]))# Show error if presentifself.controller.error:returnft.Center(content=ft.Column([ft.Icon(ft.icons.ERROR,color=ft.colors.RED),ft.Text(f"Error: {self.controller.error}")]))# Show usersreturnft.Column([ft.Text(f"Users ({len(self.controller.users)})",size=24),ft.Column([ft.ListTile(title=ft.Text(user["name"]),subtitle=ft.Text(user["email"]))foruserinself.controller.users])])
classHomePage(FletXPage):defbuild_drawer(self):"""Override to create drawer"""returnft.NavigationDrawer(controls=[ft.NavigationDrawerDestination(label="Home",icon=ft.icons.HOME),ft.NavigationDrawerDestination(label="Products",icon=ft.icons.SHOPPING_BAG),ft.NavigationDrawerDestination(label="Settings",icon=ft.icons.SETTINGS)])defbuild(self):returnft.Column([ft.ElevatedButton("Open Drawer",on_click=lambda_:self.open_drawer())])
classResponsivePage(FletXPage):def__init__(self):super().__init__()self.window_width=0self.window_height=0defon_init(self):# Listen to window resizeself.on_resize(self._on_window_resize)# Listen to media changes (orientation)self.on_media_change(self._on_media_change)# Listen to brightness changesself.on_brigthness_change(self._on_brightness_change)def_on_window_resize(self,e):"""When window is resized"""self.window_width=e.widthself.window_height=e.heightprint(f"Window: {self.window_width}x{self.window_height}")self.refresh()def_on_media_change(self,e):"""When orientation changes"""print(f"Media changed: {e.data}")self.refresh()def_on_brightness_change(self,e):"""When system brightness changes"""print(f"Brightness changed")@obxdefbuild(self):returnft.Text(f"{self.window_width}x{self.window_height}")
classSearchPage(FletXPage):def__init__(self):super().__init__(enable_keyboard_shortcuts=True)self.search_input=ft.TextField()defon_init(self):# Listen to all keyboard eventsself.on_keyboard(self._on_keyboard)# Or register specific shortcutsself.add_keyboard_shortcut("ctrl+f",self._focus_search,"Focus search")self.add_keyboard_shortcut("escape",self._clear_search,"Clear search")def_on_keyboard(self,e):"""Handle any keyboard event"""ife.key=="Enter":self._do_search()def_focus_search(self):self.search_input.focus()def_clear_search(self):self.search_input.value=""self.search_input.update()def_do_search(self):print(f"Searching for: {self.search_input.value}")defbuild(self):returnft.Column([self.search_input,ft.Text("Shortcuts: Ctrl+F to focus, Escape to clear")])
classInfiniteScrollPage(FletXPage):def__init__(self):super().__init__()self.list_view=ft.ListView()defon_init(self):self.on_scroll(self._on_scroll)def_on_scroll(self,e):"""When user scrolls"""print(f"Scroll offset: {e.offset}")# Load more items when near bottomife.offset>0.8:# 80% scrolledself._load_more_items()def_load_more_items(self):print("Loading more items...")defbuild(self):returnft.Column([self.list_view])
classGesturesPage(FletXPage):def__init__(self):super().__init__(enable_gestures=True)defon_init(self):# Handle tapself.on_tap(self._on_tap)# Handle long pressself.on_long_press(self._on_long_press)# Handle scale/zoomself.on_scale(self._on_scale)def_on_tap(self,e):print(f"Tapped at {e.local_x}, {e.local_y}")def_on_long_press(self,e):print(f"Long pressed at {e.local_x}, {e.local_y}")def_on_scale(self,e):print(f"Scale: {e.scale}")defbuild(self):returnft.Column([ft.Text("Tap, long press, or scale me!")])
classDialogsPage(FletXPage):defbuild(self):returnft.Column([ft.ElevatedButton("Alert",on_click=self._show_alert),ft.ElevatedButton("Confirm",on_click=self._show_confirm),ft.ElevatedButton("Loader",on_click=self._show_loader),ft.ElevatedButton("Snackbar",on_click=self._show_snackbar)])def_show_alert(self,e):dialog=ft.AlertDialog(title=ft.Text("Alert"),content=ft.Text("This is an alert message"),actions=[ft.TextButton("OK")])self.page_instance.dialog=dialogdialog.open=Trueself.page_instance.update()def_show_confirm(self,e):dialog=ft.AlertDialog(title=ft.Text("Confirm?"),content=ft.Text("Are you sure?"),actions=[ft.TextButton("Cancel"),ft.TextButton("Yes",style=ft.ButtonStyle(color=ft.colors.RED))])self.page_instance.dialog=dialogdialog.open=Trueself.page_instance.update()def_show_loader(self,e):self.show_loader(ft.ProgressRing())def_show_snackbar(self,e):snack=ft.SnackBar(ft.Text("Operation completed!"))self.page_instance.snack_bar=snacksnack.open=Trueself.page_instance.update()
classEffectsPage(FletXPage):def__init__(self):super().__init__()self.controller=MyController()defon_init(self):# Watch for changesself.watch(self.controller.username,self._on_username_change)# Watch multipleself.watch_multiple([self.controller.email,self.controller.phone],self._on_contact_change)# Add effect with cleanupself.add_effect(self._setup_listener,cleanup_fn=self._cleanup_listener)def_on_username_change(self):print(f"Username changed to: {self.controller.username}")def_on_contact_change(self):print("Contact info changed")def_setup_listener(self):print("Setting up listener...")def_cleanup_listener(self):print("Cleaning up listener...")defbuild(self):returnft.Text("Effects demo")
# ✅ GoodclassMyPage(FletXPage):defon_init(self):# Page is now visibleself.controller.load_data()# ❌ AvoidclassMyPage(FletXPage):def__init__(self):# Page not visible yet!self.controller.load_data()
# ✅ Good - use create_rx_* for initializationclassMyController(FletXController):def__init__(self):super().__init__()self.count=self.create_rx_int(0)self.name=self.create_rx_str("")self.items=self.create_rx_list([])# ✅ Good - use set_* methods for updatesself.controller.set_loading(True)self.controller.set_error("Error message")# ❌ Avoid - don't use RxInt/RxStr directlyself.controller.count=RxInt(0)# Wrong!# ❌ Avoid - don't set reactive properties directlyself.controller.is_loading=True# Won't trigger updates
# ✅ Good - one responsibilityclassUserListPage(FletXPage):pass# ❌ Avoid - too many responsibilities classAdminPage(FletXPage):# Shows users, analytics, settings, logs...pass