androidengineers.Book a session

Storage and lifecycle behavior

PocketChat history and recovery

articleSelf-paced

Local persistence is not cloud memory

Room stores the learner's conversations and saved answers locally. Gemini only sees the context explicitly passed to each request. The baseline's LibraryRow stores one serialized aggregate; it does not have a normalized SQL table per message. This makes a small teaching sample easy to inspect but rewrites the aggregate and searches it in memory. Large archives should move to indexed, per-conversation/message entities with explicit migrations.

package com.androidengineers.pocketchat.data

import androidx.room.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString

// Each conversation is an aggregate. The sample does not need cross-message SQL queries yet.
@Entity data class LibraryRow(@PrimaryKey val id: Int = 1, val json: String)
@Dao interface LibraryDao {
    @Query("SELECT * FROM LibraryRow WHERE id = 1") suspend fun read(): LibraryRow?
    @Upsert suspend fun write(row: LibraryRow)
}
@Database(entities = [LibraryRow::class], version = 1, exportSchema = true)
abstract class ChatDatabase: RoomDatabase() { abstract fun library(): LibraryDao }
class RoomLibraryStore(private val dao: LibraryDao): LibraryStore {
    private val json = Json { ignoreUnknownKeys = true }
    override suspend fun load(): Library = dao.read()?.let { json.decodeFromString<Library>(it.json) } ?: Library()
    override suspend fun save(library: Library) = dao.write(LibraryRow(json = json.encodeToString(library)))
}

Serialization happens at the storage boundary rather than in composables. Room's suspend DAO methods keep database access main-safe. The repository publishes UI state immediately and sends snapshots through a conflated channel. During streaming it checkpoints partial text at most roughly four times per second; terminal states trigger another write. That is a target policy, not a promise that every displayed character survives a sudden process kill.

Distinguish lifecycle events

EventExpected behavior
Rotation / Activity recreationSame app repository; draft and selection restored; no duplicate request
Switch conversationStop active generation; restore the selected draft
App backgroundsStop generation and clear in-memory debug credentials
Process is killedHTTP session is gone; last persisted active turn becomes interrupted on next load
Delete source conversationRemove local source; keep independently saved answer snapshots

Saved copies deliberately survive source deletion. Their source button becomes unavailable. Clear-all removes both history and saved answers. Neither operation removes data from a provider's infrastructure.

Failure should not destroy evidence

If the initial load fails, the repository does not overwrite the existing store with an empty library. If a later save fails, the UI can still have state that was not persisted; communicate that instead of pretending data is safe. Test the store boundary with controlled exceptions.

Check your understanding

An Activity recreation test and a repository restart test are complementary, but neither alone proves every process-death UI path. Explain what was actually recreated, what remained alive, and which data was read from disk. Keep backup behavior in the manifest/data-extraction rules aligned with the promise made to learners.

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 history and recovery | Gemini Chat for Android with PocketChat | Android Engineers