androidengineers.Book a session

State ownership and screen actions

PocketChat Compose state

articleSelf-paced

One owner for each kind of state

The screen should not open network connections during recomposition. PocketChatApp collects repository and ViewModel flows with lifecycle awareness and passes actions to event handlers. ChatViewModel owns selected-conversation and draft state using SavedStateHandle. ChatRepository owns conversation state and generation. PocketChatApplication gives the repository an application-level scope, so rotation does not accidentally create a second request.

Read this ViewModel excerpt; it is existing source, not a replacement to paste:

fun draft(text: String) { savedState["draft"] = text.take(6000); selected.value?.let { savedState["draft:$it"] = text.take(6000) } }
    fun open(id: String?) { if(selected.value != id) { repository.stop(); savedState["draft"] = id?.let { savedState.get<String>("draft:$it") } ?: "" }; savedState["conversation"] = id }
    fun newChat(prompt: String = "") { open(repository.create()); draft(prompt) }
    fun send() { val id = selected.value ?: return; if(repository.send(id, draft.value)) draft("") }

The successful-send condition matters: the draft is cleared only when the repository accepts the request. If credentials are missing, send returns false and preserves what the learner typed. Opening another conversation cancels active work and restores that conversation's draft rather than moving one draft between unrelated chats.

Trace a user action

PocketChat request ownership

Tap Send → ViewModel reads the selected ID and draft → repository validates readiness and request state → repository updates observable conversation state → Compose renders the new turn. Network events later update the same turn. The composable does not maintain a competing copy of the answer.

Layout is part of correctness

The composer lives in Scaffold's bottom bar. The snackbar is therefore placed above it. During development, a snackbar covered the in-content composer and intercepted a tap even though the Send callback itself was correct. An accessible-action test alone did not reveal the real pointer overlap; the final normal-tap journey did.

Insets and the IME must cooperate: the content consumes Scaffold padding, while the bottom composer handles keyboard and navigation-bar padding. Check the app with the keyboard visible, a multiline draft, and an active snackbar.

Check your understanding

Why not own generation in rememberCoroutineScope on a conversation composable? Its lifetime follows composition, not the product's chosen session policy. Explicit owners let you distinguish rotation, switching conversations, backgrounding, and process death. Backgrounding deliberately stops this sample's generation; process death is recovered from persisted partial state.

Study guide · Hands-on codelab · Pinned Android source

YOUR LEARNING JOURNEY

0 of 23 available lessons completed

Progress saved in this browser. No account needed.
PocketChat Compose state | Gemini Chat for Android with PocketChat | Android Engineers