Let each layer own one kind of work
Compose renders StoriesState and sends events such as begin, photo, generate and stop. StoriesViewModel coordinates model access, scene images, story persistence and generation. Those interfaces allow deterministic tests without a camera, network or native model. Their purpose is testable boundaries, not adding an interface to every class.
The application wires dependencies manually. Room owns persisted rows; ModelStore owns artifact setup; LocalStoryGenerator owns the engine/conversation operation. The UI does not construct a new native engine during recomposition. This separation prevents a visual state change from accidentally loading gigabytes of model state again.
interface StoryGenerator {
suspend fun generate(prompt: String, image: File?, onChunk: (String) -> Unit)
}
The contract permits streamed chunks and a terminal return or failure. A fake implementation can delay or emit late chunks to test the ViewModel's behavior. It cannot establish correctness of LiteRT's callback or device allocation behavior. Keep that distinction in test reports.
Follow the state transitions
Create → capture/select → reader draft
Generate → missing model? setup : busy + streamed draft
Stop → stopping → retained partial draft
Accept → saved scene + cleared transient draft
Read generate() and the state.copy calls. The busy/importing guards prevent overlapping actions, while the callback checks activity before accepting output. Rotation retains the ViewModel; process death does not. A single Boolean is not a complete production state machine: the preview separates busy, importing and stopping, but a sealed operation type could eliminate impossible combinations in a later refactor.
Practice and expected answer
Run StoriesViewModelTest. Identify the test that blocks duplicate generation and the test that ignores chunks after cancellation. Draw which component owns Job, native engine, Room row and Compose state. Then reason through two rapid taps on Generate: only one operation should run. A second tap must not append a duplicate opening or overwrite the in-flight operation reference.
Inspect the sample-story guard. A handwritten sample remains read-only for generation in this implementation so its source cannot be confused with actual model output. Explain why the fake in a unit test must never become an invisible runtime fallback.