| name | robius-state-management |
| description | CRITICAL: Use for Robius state management patterns. Triggers on: AppState, persistence, theme switch, 状态管理, Scope::with_data, save state, load state, serde, 状态持久化, 主题切换 |
Robius State Management Skill
Best practices for state management and persistence in Makepad applications based on Robrix and Moly codebases.
Source codebases:
- Robrix: Matrix chat client - AppState, SelectedRoom, persistence via serde
- Moly: AI chat application - Central Store pattern, async initialization, Preferences
Triggers
Use this skill when:
- Designing application state structure
- Implementing state persistence
- Passing state through widget tree
- Managing UI state across sessions
- Keywords: app state, makepad state, persistence, Scope::with_data, save state, load state
Production Patterns
For production-ready state management patterns, see the _base/ directory:
| Pattern | Description |
|---|
| 06-global-registry | Global widget registry with Cx::set_global |
| 07-radio-navigation | Tab-style navigation with radio buttons |
| 10-state-machine | Enum-based state machine widgets |
| 11-theme-switching | Multi-theme support with apply_over |
| 12-local-persistence | Save/load user preferences |
AppState Structure
Core State Definition
use serde::{Serialize, Deserialize};
use std::collections::HashMap;
use matrix_sdk::ruma::OwnedRoomId;
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct AppState {
pub selected_room: Option<SelectedRoom>,
pub saved_layout_state: SavedLayoutState,
pub saved_state_per_item: HashMap<OwnedRoomId, SavedLayoutState>,
#[serde(skip)]
pub logged_in: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum SelectedRoom {
JoinedRoom { room_name_id: RoomNameId },
InvitedRoom { room_name_id: RoomNameId },
Space { space_name_id: RoomNameId },
}
impl SelectedRoom {
pub fn room_id(&self) -> &OwnedRoomId {
match self {
Self::JoinedRoom { room_name_id } => room_name_id.room_id(),
Self::InvitedRoom { room_name_id } => room_name_id.room_id(),
Self::Space { space_name_id } => space_name_id.room_id(),
}
}
pub fn upgrade_invite_to_joined(&mut self, room_id: &RoomId) -> bool {
match self {
Self::InvitedRoom { room_name_id } if room_name_id.room_id() == room_id => {
let name = room_name_id.clone();
*self = Self::JoinedRoom { room_name_id: name };
true
}
_ => false,
}
}
}
impl PartialEq for SelectedRoom {
fn eq(&self, other: &Self) -> bool {
self.room_id() == other.room_id()
}
}
impl Eq for SelectedRoom {}
Layout/Dock State Persistence
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct SavedLayoutState {
pub layout_items: HashMap<LiveIdSerde, LayoutItemSerde>,
pub open_items: HashMap<LiveIdSerde, SelectedRoom>,
pub item_order: Vec<SelectedRoom>,
pub selected_item: Option<SelectedRoom>,
}
#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct LiveIdSerde(pub u64);
impl From<LiveId> for LiveIdSerde {
fn from(id: LiveId) -> Self {
Self(id.0)
}
}
impl From<LiveIdSerde> for LiveId {
fn from(s: LiveIdSerde) -> Self {
LiveId(s.0)
}
}
State Propagation via Scope
Passing State Through Widget Tree
impl AppMain for App {
fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
self.match_event(cx, event);
let scope = &mut Scope::with_data(&mut self.app_state);
self.ui.handle_event(cx, event, scope);
}
}
Accessing State in Child Widgets
impl Widget for RoomScreen {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
if let Some(app_state) = scope.data.get::<AppState>() {
if let Some(selected) = &app_state.selected_room {
self.update_for_room(cx, selected);
}
}
self.view.handle_event(cx, event, scope);
}
}
Modifying State
impl Widget for RoomsList {
fn handle_event(&mut self, cx: