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
```
```
Snippet 7
```
self.label1.set_text(self.value)
self.label2.set_text(self.value)
self.repeat_view.set_items(self.array)
```
Snippet 8
```
self.value.set("newvalue")
self.array.set([5, 6, 7, 8])
```
Snippet 9
```
def get_gamepad(self):
return None
```
Snippet 10
```
def get_gamepad(self):
return "componentname" # Replace with an existing component name.
```
Snippet 11
```
from appsudo import AppContext
display_size = AppContext.current().displaySize()
```
Snippet 12
```
from appsudo import AppRuntime
is_dark_mode = AppRuntime.IS_DARK_MODE
```
Snippet 13
```
from appsudo import AppRuntime
is_web = AppRuntime.is_web()
is_android = AppRuntime.is_android()
is_ios = AppRuntime.is_ios()
```
Snippet 14
```
def get_shareable(self):
# Dynamic: block sharing while sensitive content is on screen.
if self.is_showing_sensitive_details:
return False
return self.is_paying_user
```
Snippet 15
```
def get_data(self):
return {
"level": self.level,
"score": self.score,
"session_id": self.session_id,
}
```
Detailed Coverage Map
- The entries below were derived from the section structure in `appsudo.com/doc`.
Categories
- Lifecycle Hooks
- App Modes
- Two-way Binding
- Game Controller
- Screen Size
- Light/Dark Mode
- Running Platform
- Sharing & Data