TL;DR: A FletXService is a reusable component for handling external systems (APIs, databases, hardware, storage). Services have a lifecycle (on_start(), on_ready(), on_stop(), on_dispose()), built-in HTTP client, state management (IDLE, LOADING, READY, ERROR), and data storage. Use them to keep controllers lean and focused on business logic.
classUserController(FletXController):def__init__(self):super().__init__()self.user=self.create_rx_dict({})deffetch_user(self,user_id):# API call logic mixed with controllerimportrequeststry:response=requests.get(f"https://api.example.com/users/{user_id}")self.user.value=response.json()exceptExceptionase:self.error_message.value=str(e)# Parse response, validate, cache, retry logic...# All here in the controller!
classDatabaseService(FletXService):def__init__(self):super().__init__(name="DatabaseService")self.connection=Nonedefon_start(self):"""Called when service starts"""print("Opening database connection...")self.connection=self._open_connection()defon_ready(self):"""Called when service is ready"""print("Database service is ready!")defon_stop(self):"""Called when service stops"""print("Closing database connection...")ifself.connection:self.connection.close()defon_dispose(self):"""Called during final cleanup"""print("Database service disposed")self.connection=Nonedef_open_connection(self):# Simulate opening connectionreturn{"connected":True}
classAsyncAPIService(FletXService):asyncdefon_start_async(self):"""Async version of on_start"""print("Starting async service...")awaitself._initialize_async()asyncdefon_stop_async(self):"""Async version of on_stop"""print("Stopping async service...")awaitself._cleanup_async()asyncdef_initialize_async(self):# Async initializationpassasyncdef_cleanup_async(self):# Async cleanuppass
service=MyService(auto_start=False)# Don't auto-start# Start the serviceservice.start()# Check if readyifservice.is_ready:result=service.do_something()# Restart the serviceservice.restart()# Stop the serviceservice.stop()# Dispose and cleanupservice.dispose()
# Check current stateprint(service.state)# ServiceState.READY# Convenience propertiesifservice.is_ready:# Service is operationalpassifservice.is_loading:# Service is initializingpassifservice.has_error:# Service has errorerror=service.errorprint(f"Error: {error}")
fromfletx.coreimportFletXService,HTTPClientclassGitHubService(FletXService):def__init__(self):super().__init__(name="GitHubService",http_client=HTTPClient("https://api.github.com"))defget_user(self,username):# GET requestresponse=self.http_client.get(f"/users/{username}")returnresponse.json()defget_repos(self,username):# GET with parametersresponse=self.http_client.get(f"/users/{username}/repos",params={"per_page":10})returnresponse.json()defcreate_gist(self,description,content):# POST requestresponse=self.http_client.post("/gists",json={"description":description,"files":{"code.py":{"content":content}}})returnresponse.json()defupdate_repo(self,owner,repo,data):# PATCH requestresponse=self.http_client.patch(f"/repos/{owner}/{repo}",json=data)returnresponse.json()
classUserCacheService(FletXService):def__init__(self):super().__init__(name="UserCacheService")defon_start(self):# Initialize with dataself.set_data("users",{})self.set_data("last_update",None)defcache_user(self,user_id,user_data):users=self.get_data("users",{})users[user_id]=user_dataself.set_data("users",users)defget_cached_user(self,user_id):users=self.get_data("users",{})returnusers.get(user_id)defclear_cache(self):self.clear_data()
# Set individual dataservice.set_data("key","value")# Get individual data with defaultvalue=service.get_data("key",default="default_value")# Get all data (copy)all_data=service.data# Clear all dataservice.clear_data()
classPaymentService(FletXService):def__init__(self):super().__init__(http_client=HTTPClient("https://payment.api.com"))defprocess_payment(self,amount,card):try:response=self.http_client.post("/payment",json={"amount":amount,"card":card})returnresponse.json()exceptExceptionase:# Set error stateself.set_error(e)returnNonedefon_state_changed(self):"""Called when service state changes"""ifself.has_error:print(f"Payment error: {self.error}")
# Check for errorsifservice.has_error:error=service.errorprint(f"Error occurred: {error}")# Set error manuallyservice.set_error(Exception("Something went wrong"))
fromfletx.coreimportFletXService,FletXController,FletXPage,HTTPClientfromfletx.decoratorsimportobximportfletasft# Service LayerclassPostAPIService(FletXService):def__init__(self):super().__init__(name="PostAPIService",http_client=HTTPClient("https://jsonplaceholder.typicode.com"))defon_start(self):print("Post API Service started")self.set_data("posts_cache",[])deffetch_posts(self):"""Fetch all posts"""try:response=self.http_client.get("/posts")posts=response.json()self.set_data("posts_cache",posts)returnpostsexceptExceptionase:self.set_error(e)return[]deffetch_post_details(self,post_id):"""Fetch single post with comments"""try:post=self.http_client.get(f"/posts/{post_id}").json()comments=self.http_client.get(f"/posts/{post_id}/comments").json()return{"post":post,"comments":comments}exceptExceptionase:self.set_error(e)returnNonedefcreate_post(self,title,body,user_id):"""Create a new post"""try:response=self.http_client.post("/posts",json={"title":title,"body":body,"userId":user_id})returnresponse.json()exceptExceptionase:self.set_error(e)returnNone# Controller LayerclassPostController(FletXController):def__init__(self):super().__init__()self.posts=self.create_rx_list([])self.selected_post=self.create_rx_dict({})self.post_service=PostAPIService()# Computedself.post_count=self.create_computed(lambda:len(self.posts))defon_ready(self):# Load posts when controller is readyself.load_posts()defload_posts(self):self.set_loading(True)try:posts=self.post_service.fetch_posts()self.posts.value=postsself.emit_local("posts_loaded",len(posts))finally:self.set_loading(False)defselect_post(self,post_id):details=self.post_service.fetch_post_details(post_id)ifdetails:self.selected_post.value=detailsdefcreate_post(self,title,body):result=self.post_service.create_post(title,body,1)ifresult:self.posts.append(result)self.emit_local("post_created",result)# View LayerclassPostPage(FletXPage):def__init__(self):super().__init__(padding=20)self.controller=PostController()self.title_input=ft.TextField(label="Title")self.body_input=ft.TextField(label="Body",multiline=True,min_lines=3)defon_init(self):self.controller.on_local("posts_loaded",self._on_posts_loaded)def_on_posts_loaded(self,event):print(f"Loaded {event.data} posts")def_create_post(self):title=self.title_input.valuebody=self.body_input.valueiftitleandbody:self.controller.create_post(title,body)self.title_input.value=""self.body_input.value=""self.refresh()@obxdefbuild(self):# Loading stateifself.controller.is_loading:returnft.Center(content=ft.ProgressRing())# Error stateifself.controller.error_message:returnft.Center(content=ft.Column([ft.Icon(ft.icons.ERROR,color=ft.colors.RED),ft.Text(f"Error: {self.controller.error_message}"),ft.ElevatedButton("Retry",on_click=lambda_:self.controller.load_posts())]))# Posts listreturnft.Column([# Headerft.Text(f"Posts ({self.controller.post_count})",size=24,weight="bold"),# Create post formft.Card(content=ft.Container(content=ft.Column([self.title_input,self.body_input,ft.ElevatedButton("Create Post",on_click=lambda_:self._create_post())],spacing=10),padding=15)),# Posts listft.ListView([ft.Card(content=ft.Container(content=ft.Column([ft.Text(post["title"],weight="bold",size=14),ft.Text(post["body"][:100]+"...",size=12,color=ft.colors.GREY),ft.Row([ft.TextButton("View Details",on_click=lambda_,pid=post["id"]:self.controller.select_post(pid))])],spacing=5),padding=10))forpostinself.controller.posts])],scroll=ft.ScrollMode.AUTO,spacing=15)
# ✅ Good - service handles one concernclassUserAPIService(FletXService):deffetch_users(self):passdeffetch_user(self,user_id):pass# ❌ Avoid - service doing too muchclassAppService(FletXService):deffetch_users(self):passdeffetch_posts(self):passdeffetch_comments(self):passdefsend_email(self):pass
# ✅ Good - inject service dependenciesclassUserController(FletXController):def__init__(self,user_service):super().__init__()self.user_service=user_service# ❌ Avoid - create service directlyclassUserController(FletXController):def__init__(self):super().__init__()self.user_service=UserAPIService()# Hard to test
# ✅ Good - cleanup in on_stopdefon_start(self):self.websocket=WebSocket()self.websocket.connect()defon_stop(self):ifself.websocket:self.websocket.close()# ❌ Avoid - leaving resources opendefon_start(self):self.websocket=WebSocket()self.websocket.connect()# No cleanup!
# ✅ Good - initialize datadefon_start(self):self.set_data("cache",{})self.set_data("config",{"timeout":30})# ❌ Avoid - uninitialized datadeffetch_from_cache(self,key):cache=self.get_data("cache",{})# May not exist yet