App Lifecycle Source section: `https://appsudo.com/doc#app-lifecycle` Lifecycle hooks (`#app-lifecycle-hooks`) Hook order 1. `on_launch` 2. `set_data(metadata)` 3. `run` 4. `on_ready` 5. `on_app_mode_change(app_mode)` on each later mode change Hook meanings - `on_launch`: pre-render setup and preloading. - `set_data(metadata)`: receives runtime metadata before render. - `run`: the place where the app renders itself. - `on_ready`: called after the app is fully ready. - `on_app_mode_change(app_mode)`: mode transition handler. Metadata described by the docs - `config` - decrypted secrets - `profileId` - `profileUserName` - `profileAvatar` - `appId` - `appName` - `appVersion` App modes (`#app-lifecycle-modes`) - `FULLSCREEN`: default. - `WIDGET`: small centered window. - `POPUP`: bottom dialog style view using most of the screen. - `MINIMIZE`: docked into a feed post. Mode-related rules - In `on_app_mode_change(app_mode)`, `app_mode` is the new mode. - `self.get_app_mode()` returns the previous mode. - `self.set_app_mode(mode)` can be used to change modes, except `MINIMIZE`. - Override `get_allowed_mode()` to limit supported modes. Two-way Binding (`#app-lifecycle-two-way-binding`) - Manual component updates can require assigning the same value in several places and calling methods such as `refresh()`. - Import `State` from `runtime` when one value or array needs to drive multiple components. - Create the initial `State` values in the app's data class or other class exposed through the existing inflater. - Bind a state in ASXML with expressions such as `{data.value}` and `{data.array}`. - A `State` can also be passed directly to the matching component method in Python. - Call `state.set(new_value)` to update every bound component and perform the required component refresh. - State binding keeps conditional update logic, repeated assignments, and manual refresh calls out of unrelated app code. Game Controller (`#app-lifecycle-game-controller`) - Gamepad selection applies when `metadata.json` sets `"type": "Game"`. - Override `get_gamepad()` on the `App` subclass to hide the controller or load a different controller component. - Without an override, or when `get_gamepad()` returns `"default"`, Appsudo loads the existing `sudogamepad` component. - Return `None` to completely hide the gamepad. - Return an existing component name to dynamically load that component for the game. - A custom return value must exactly match an existing component name. - Browse available component names in the Appsudo Store: `https://store.appsudo.com`. Screen Size (`#app-lifecycle-screen-size`) - Read the current display size with `AppContext.current().displaySize()`. - Use the result only for broad checks, such as determining whether the width is larger than the height for a rotated device or tablet-style layout. - Do not build complex application logic around exact dimensions. - The documentation does not prescribe width or height accessor syntax. Light/Dark Mode (`#app-lifecycle-display-mode`) - Read the current display mode from `AppRuntime.IS_DARK_MODE`. - Use it to choose theme-appropriate colors, assets, and presentation. Running Platform (`#app-lifecycle-platform`) - Detect web, Android, or iOS with the `AppRuntime` methods shown below. - Keep rendering and UI behavior platform agnostic. - Limit platform values to non-UI uses such as API request context or app-specific statistics. - Platform information is already available in the Appsudo developer portal, so most apps do not need to collect it themselves. Sharing & Data (`#app-lifecycle-sharing`) - Override `get_shareable()` to allow or block sharing to feed/DM. - Override `get_data()` to return serializable state for sharing/continuation. - Shared state is later delivered back through `set_data(metadata)`. Code Snippets From Docs - The blocks below were extracted from the corresponding section of `appsudo.com/doc`. Snippet 1 ``` def on_launch(self): # App not rendered yet. Initialize or preload data here. self.pending_user_id = None def set_data(self, metadata): # Pre-render. metadata includes config, decrypted secrets, # profileId, profileUserName, profileAvatar, appId, appName, appVersion. # Store at runtime to register users in an external DB, build a profile, etc. self.profile_id = metadata.get("profileId") self.profile_user_name = metadata.get("profileUserName") self.app_id = metadata.get("appId") def run(self): # Renders the app to the device (mobile and web). self.render() goes here. self.render(self.ui_view) def on_ready(self): # App is fully ready to perform actions. self._load_initial_state() def on_app_mode_change(self, app_mode): # app_mode = the NEW mode the app is transitioning to. # self.get_app_mode() = the PREVIOUS mode, prior to this change. # Use both to decide how to transition the visualization. self._transition(from_mode=self.get_app_mode(), to_mode=app_mode) ``` Snippet 2 ``` def on_app_mode_change(self, app_mode): # app_mode = NEW mode transitioning to # self.get_app_mode() = PREVIOUS mode, prior to this change if app_mode == "WIDGET": self._render_compact(from_mode=self.get_app_mode()) elif app_mode == "POPUP": self._render_sheet(from_mode=self.get_app_mode()) elif app_mode == "MINIMIZE": self._render_docked(from_mode=self.get_app_mode()) ``` Snippet 3 ``` def get_allowed_mode(self): return ["FULLSCREEN", "WIDGET", "POPUP"] ``` Snippet 4 ``` # Single value value = "newvalue" self.text = f"{value} - something" self.text2 = value # Array self.repeat_view.set_items(newarray) self.repeat_view.refresh() ``` Snippet 5 ``` from runtime import State # Initial state self.value = State("initial value") self.array = State([1, 2, 3]) ``` Snippet 6 ```