diff --git a/docs/assets/diagrams/di.svg b/docs/assets/diagrams/di.svg new file mode 100644 index 0000000..c3349cb --- /dev/null +++ b/docs/assets/diagrams/di.svg @@ -0,0 +1,32 @@ + + + + + + + + + Controller + asks for dependency + + + DI Container + resolves/creates + + + Service + e.g., UserService + + + Controller uses Service + + + + + + + diff --git a/docs/assets/diagrams/reactivity.svg b/docs/assets/diagrams/reactivity.svg new file mode 100644 index 0000000..f1800b3 --- /dev/null +++ b/docs/assets/diagrams/reactivity.svg @@ -0,0 +1,33 @@ + + + + + + + + + Widget event + e.g., on_click() + + + Controller method + updates Rx value + + + Reactive state (Rx*) + change detected + + + Widgets re-render + bound via lambda/decorators + + + + + + + diff --git a/docs/assets/diagrams/routing.svg b/docs/assets/diagrams/routing.svg new file mode 100644 index 0000000..862e519 --- /dev/null +++ b/docs/assets/diagrams/routing.svg @@ -0,0 +1,37 @@ + + + + + + + + + URL change + /user/42 + + + Router + matches route + + + Create Page + UserPage + + + Attach Controller + UserController + + + build() β†’ UI + + + + + + + + diff --git a/docs/getting-started/architecture.md b/docs/getting-started/architecture.md index f50f15d..1f806e9 100644 --- a/docs/getting-started/architecture.md +++ b/docs/getting-started/architecture.md @@ -1,163 +1,266 @@ -# πŸ—οΈ FletX Architecture +# πŸ—οΈ FletX Architecture (Progressive Guide) -FletX is built on a **modular and reactive architecture** designed to help developers structure Flet applications in a clean, maintainable, and scalable way. It is inspired by principles like separation of concerns and dependency injection. +FletX is a **modular, reactive application layer** for [Flet](https://flet.dev). Think of it as the scaffolding that helps your UI, logic, and navigation work together cleanly. + +This guide builds up from the big picture to the details, with diagrams and tiny examples you can copy and adapt. --- -## πŸ“š Overview +## πŸ—ΊοΈ Big Picture -FletX architecture revolves around three **core components**: +Analogy: FletX is like a theater production. -1. **Pages (`FletXPage`)** – declarative, reactive UI components. -2. **Controllers (`FletXController`)** – business logic and state management. -3. **Services (optional)** – reusable utilities for API calls, database access, etc. +- The **Page** is the stage where the scene is rendered. +- The **Controller** is the director that decides what happens next. +- **Reactive state (Rx)** is the script prompts: when a line changes, actors (widgets) immediately react. +- **Services** are backstage crews (API, storage, utilities). +- The **Router** is the stage manager who swaps scenes. ---- +High-level flow: + +```text +User action β†’ Controller updates reactive state β†’ Widgets re-render +``` -### πŸ”„ Typical Flow +
+ Sequence showing widget event triggering controller method, which updates an Rx value, causing dependent widgets to re-render. +
Reactivity flow: events update reactive state; bound widgets re-render automatically.
+
-A typical user interaction flows like this: +Navigation flow: -```plaintext -User Action β†’ Controller Logic β†’ State Update β†’ UI Re-render +```text +Route change β†’ Page created β†’ Controller available β†’ build() renders UI ``` -When routing/navigation happens, it flows like this: +
+ Route change matched by router, page creation, controller attachment, then build to render UI. +
Routing flow: router matches URL, creates the page, attaches its controller, then renders.
+
-```plaintext -Routing β†’ Page Instantiation β†’ Controller Injection β†’ Build UI -``` +!!! tip "How to read these diagrams" + + - Boxes represent **components** (Page, Controller, Service, etc.). + - Arrows indicate **direction of flow** (events β†’ state β†’ UI). + - Bold labels are the **key step** in each stage. + - White diagram backgrounds ensure legibility in **dark mode**; use the ASCII diagrams alongside if you prefer text. --- -## 🧱 Core Building Blocks +## 🧱 Core Pieces (at a glance) -### 1. FletXPage +- Page (`FletXPage`): builds UI in `build()` and accesses its controller via `self.ctrl` (or any variable name you choose). +- Controller (`FletXController`): holds logic and reactive state (e.g., `RxInt`, `RxStr`). +- Services (optional): reusable dependencies resolved via DI (dependency injection). +- Router (`router_config`): maps paths to pages; supports dynamic params and guards. -A **FletXPage** is a class that represents a visual page (screen) in your app. It inherits from `FletXPage` and defines a `build()` method that returns a reactive Flet UI. +Simple data flow: -#### Example: +```text +[Widget event] ──▢ [Controller method] ──▢ [Rx value changes] ──▢ [UI auto-updates] +``` + +--- + +## βœ‹ First Contact: 30‑second example ```python -class HomePage(FletXPage): - ctrl = HomeController() +import flet as ft +from fletx.core import FletXPage, FletXController, RxInt +from fletx.decorators import obx + +class CounterController(FletXController): + def __init__(self): + self.count = RxInt(0) + super().__init__() + +class CounterPage(FletXPage): + ctrl = CounterController() # 'ctrl' is a convention; you can name it anything + + @obx + def counter_text(self): + return ft.Text(f"Count: {self.ctrl.count.value}", size=40) def build(self): return ft.Column([ - ft.Text(lambda: str(self.ctrl.counter()), size=40), - ft.ElevatedButton("Increment", on_click=lambda e: self.ctrl.counter.increment()) + self.counter_text(), + ft.ElevatedButton("+1", on_click=lambda e: self.ctrl.count.increment()) ]) ``` +What to notice: + +- The `@obx` decorator makes the method reactive β€” it re-renders when `count` changes. +- The button calls a controller method that mutates reactive state. +- `ctrl` is just a variable name; you can use any name you prefer. + --- -### 2. FletXController +## πŸ”„ Reactivity (how updates propagate) + +```text + increment() + β”‚ + β–Ό + [RxInt.count] ── change detected ──▢ widgets depending on it re-render +``` + +Key ideas: -A **FletXController** handles **business logic**, manages **reactive state**, and is tied to a specific page. It uses observable values to trigger UI updates automatically. +- Use `Rx*` types (`RxInt`, `RxStr`, `RxList`, `RxDict`, …) for observable state. +- Use `@obx` decorator or reactive decorators so widgets update automatically. +- Keep computations inside the controller; keep the page mostly declarative. -#### Example: +Tiny example: ```python -class HomeController(FletXController): +from fletx.core import FletXController, RxStr + +class HelloController(FletXController): def __init__(self): - self.counter = RxInt(0) + self.name = RxStr("World") super().__init__() + + def set_name(self, value: str): + self.name.value = value.strip() or "World" ``` -`RxInt` is a reactive object provided by FletX. Updating it automatically refreshes all widgets that depend on it. +```python +import flet as ft +from fletx.core import FletXPage +from fletx.decorators import obx + +class HelloPage(FletXPage): + ctrl = HelloController() + + @obx + def greeting(self): + return ft.Text(f"Hello, {self.ctrl.name.value}!") + + def build(self): + return ft.Column([ + self.greeting(), + ft.TextField(on_change=lambda e: self.ctrl.set_name(e.control.value)) + ]) +``` --- -## πŸ”— Navigation & Routing +## 🧭 Routing (moving between pages) -FletX provides a centralized router configuration (`router_config`) for managing navigation across your app: +Basic setup: ```python -router_config.add_route("/", HomePage) -router_config.add_route("/about", AboutPage) +from fletx.app import FletXApp +from fletx.navigation import router_config -# Or register a list of routes router_config.add_routes([ {"path": "/", "component": HomePage}, - {"path": "/settings", "component": SettingsPage} + {"path": "/user/:id", "component": UserPage}, ]) + +app = FletXApp(title="My App", initial_route="/") +app.run() ``` -> You can define dynamic routes like: +Access dynamic params inside a page: ```python -router_config.add_route("/user/:id", UserPage) -router_config.add_route("/user/*category", CategoryPage) +class UserPage(FletXPage): + ctrl = UserController() + + def build(self): + user_id = self.route_info.params["id"] + # render with user_id ``` -In your page: +Diagram: -```python -def build(self): - user_id = self.route_info.params["id"] +```text +URL change β†’ match route β†’ create Page β†’ attach Controller β†’ build() β†’ UI ``` ---- +Learn more: see `Getting Started β†’ Routing System` and `Pages (views)`. -## 🧠 Reactive State Management +--- -FletX provides **reactive variables**: `RxInt`, `RxStr`, `RxList`, etc., which track their values and trigger UI updates when modified. +## 🧩 Dependency Injection (Services and reuse) -#### Example: +Analogy: DI is a tool bench. Controllers don’t build the tools; they pick them up. ```python -class CounterController(FletXController): +class UserService: + def fetch_user(self, user_id: str) -> dict: + return {"id": user_id, "name": "Jane"} + +# Register the service with DI container (typically in main.py or app setup) +from fletx.core import FletX +FletX.put(UserService) # Register as singleton + +class UserController(FletXController): def __init__(self): - self.count = RxInt(0) + self.user_service = FletX.find(UserService) + self.user = RxDict({}) super().__init__() -class CounterPage(FletXPage): - ctrl = CounterController() + def load_user(self, user_id: str): + self.user.value = self.user_service.fetch_user(user_id) +``` - def build(self): - return MyReactiveText(rx_text=self.ctrl.count, size=200, weight="bold"), +DI flow: + +```text +[Controller] ──asks──▢ [DI container] ──returns──▢ [Service instance] ``` -> `lambda:` makes the widget reactive β€” it will re-render automatically when the value changes. +
+ Controller requests a dependency from the DI container, which returns a service instance used by the controller. +
Dependency Injection: controllers ask the container for services instead of constructing them.
+
+ +Learn more: see `Getting Started β†’ Dependency Injection` and `Services`. --- -## 🧩 Services (Optional) +## πŸ‘£ End‑to‑End Mini Walkthrough -**Services** are reusable, testable classes used for accessing APIs, databases, or any shared logic. They can be injected into controllers. +Goal: Tap a button on `CounterPage` to increment a number. -#### Example: +1) Router maps `/` β†’ `CounterPage`. +2) `CounterPage` is created with `ctrl = CounterController()`. +3) UI shows a `Text(lambda: ...)` bound to `ctrl.count`. +4) Button calls `ctrl.count.increment()`. +5) `RxInt` notifies dependents β†’ `Text` re-renders with the new value. -```python -class UserService: - def fetch_user(self, user_id): - return {"id": user_id, "name": "John Doe"} -``` - -Used in a controller: +--- -```python -class UserController(FletXController): - def __init__(self): - self.user_service = FletX.find(UserService) - self.user = RxDict({}) - super().__init__() +## 🧰 Common Patterns - def load_user(self, user_id): - self.user.value = self.user_service.fetch_user(user_id) -``` +- Keep pages thin; put logic in controllers. +- Use services for I/O and reuse (API, storage, computation helpers). +- Prefer small, focused controllers per page/feature. +- Derive view state from a few reactive primitives to avoid duplication. +- Use route params to load data in `on_init`/first build. --- -## πŸ§ͺ Minimal Architecture Example +## βœ… Best Practices + +- Name reactive variables by intent (e.g., `isLoading`, `selectedUserId`). +- Avoid mutating raw data in pages; call controller methods instead. +- Keep `build()` pure; it should read state and declare UI, not perform side-effects. +- Debounce or throttle controller methods that respond to rapid UI events. +- Centralize navigation in controller methods for testability. +- Guard your routes when needed (auth, permissions). + +--- -Here’s a minimal FletX app putting all the pieces together: +## πŸ§ͺ Minimal App Template ```python # main.py from fletx.app import FletXApp from fletx.navigation import router_config -from .pages.counter import CounterPage router_config.add_route("/", CounterPage) @@ -167,47 +270,44 @@ app.run() ```python # pages/counter.py -from fletx.core import FletXPage -from .controllers.counter import CounterController -from .components.reactive_text import MyReactiveText import flet as ft +from fletx.core import FletXPage, FletXController, RxInt +from fletx.decorators import obx + +class CounterController(FletXController): + def __init__(self): + self.count = RxInt(0) + super().__init__() class CounterPage(FletXPage): ctrl = CounterController() + @obx + def count_display(self): + return ft.Text(f"{self.ctrl.count.value}", size=40) + def build(self): return ft.Column([ - MyReactiveText(rx_text=self.ctrl.count, size=40, weight="bold"), - ft.ElevatedButton("Increment", on_click=lambda e: self.ctrl.count.increment()) + self.count_display(), + ft.ElevatedButton("Increment", on_click=lambda e: self.ctrl.count.increment()), ]) ``` -```python -# components/reactive_text.py -import flet as ft -from fletx.decorators import simple_reactive - -@simple_reactive(bindings={'value': 'text'}) -class MyReactiveText(ft.Text): - - def __init__(self, rx_text: RxStr, **kwargs): - self.text: RxStr = rx_text - super().__init__(**kwargs) -``` +--- -```python -# controllers/counter.py -from fletx.core import FletXController, RxInt +## πŸ”— Where to go next -class CounterController(FletXController): - def __init__(self): - self.count = RxInt(0) - super().__init__() -``` +- Getting Started β†’ `Pages (views)` +- Getting Started β†’ `Controllers` +- Getting Started β†’ `Routing System` +- Getting Started β†’ `State Management` +- Getting Started β†’ `Dependency Injection` +- Sample project: `Getting Started β†’ Sample Project` +- Real-world example: [Fake Shop E-commerce App](https://github.com/AllDotPy/fake-shop) --- -## βœ… Summary Table +## πŸ“š Reference Cheatsheet | Component | Responsibility | | ----------------- | ---------------------------------------- | @@ -215,13 +315,4 @@ class CounterController(FletXController): | `FletXController` | Holds business logic and reactive state | | `Rx*` objects | Reactive state (trigger UI rebuilds) | | `router_config` | Defines app navigation routes | -| Services | Shared utilities for APIs, storage, etc. | - - ---- - -## 🧠 Next Steps - -* Explore [reactive UI binding](ui/reactivity.md) -* Learn about the [Architecture](architecture.md) -* Dive into [dependency injection](guides/dependency-injection.md) \ No newline at end of file +| Services | Shared utilities for APIs, storage, etc. | \ No newline at end of file diff --git a/docs/getting-started/routing.md b/docs/getting-started/routing.md index 44c0d6c..5f498e5 100644 --- a/docs/getting-started/routing.md +++ b/docs/getting-started/routing.md @@ -6,6 +6,14 @@ FletX also provides utility functions for programmatic navigation, such as `navi --- +!!! tip "How to read routing diagrams" + + - Boxes represent **router steps** or **components**. + - Arrows indicate **navigation flow** (URL β†’ match β†’ page β†’ controller β†’ UI). + - Prefer using the Architecture guide’s diagrams as a quick mental model. + +[← Back to Architecture](architecture.md) + ## 🧭 Basic Routing Use the global `router_config` to define your app's navigation structure: @@ -268,5 +276,5 @@ go_back() ## 🧠 Next Steps * Dive into [dependency injection](dependency-injection.md) -* Explore the [sevices](services.md) +* Explore the [services](services.md) * Learn about the [Architecture](architecture.md) \ No newline at end of file diff --git a/docs/getting-started/state-management.md b/docs/getting-started/state-management.md index e121965..cb4ee6c 100644 --- a/docs/getting-started/state-management.md +++ b/docs/getting-started/state-management.md @@ -4,6 +4,16 @@ --- +!!! tip "How to read reactivity diagrams" + + - Boxes are **state holders** (Rx) or **consumers** (widgets/controllers). + + - Arrows show **state change propagation**. + + - Use the Architecture guide’s reactivity figure as a companion visual. + +[← Back to Architecture](architecture.md) + ### πŸ”„ Why reactivity matters In most frameworks, when a value changes (e.g. a user logs in), you need to manually update the UI, synchronize the state, or refresh components.