Brifing

Testing Internal Compose State with snapshotFlow

The reactive way to prevent flaky tests in complex UI testing

ProAndroidDev - Medium2 min

1What is snapshotFlow?

snapshotFlow acts as a bridge that transforms Jetpack Compose state changes into Kotlin Flow. It moves state changes from the UI render loop to the coroutine world. Ideal for scenarios like showing a 'Back to Top' button when user scrolls past the 10th item. Provides a reactive solution instead of Thread.sleep() or polling loops, preventing flaky tests.

2How the mechanism works

snapshotFlow observes State objects within it. When state changes, the block re-executes and if the result differs from last emission, Flow emits new value. While collectAsState brings Flow into Compose, snapshotFlow does the opposite - moving Compose state to coroutines. This allows processing state changes independently from UI render loop.

3Real-world use case

You can track LazyListState's firstVisibleItemIndex to detect if user passed certain items. With distinctUntilChanged() you emit only on actual changes, with filter only under specific conditions. Perfect for scenarios like triggering analytics events when user scrolls past 5th item.

4Importance in testing

Testing states not in Semantics Tree (like LazyColumn scroll position, Pager state or Animatable values) is challenging. snapshotFlow lets you observe these state changes in test thread, waiting until condition is met. Eliminates need for unreliable methods like Thread.sleep().

5Solving race conditions

When you need to click a button that appears after scrolling in a test, checking before state updates causes failure. With snapshotFlow, you can wait for scrollState.value to reach specific value. Using runBlocking inside waitUntil, you can exit flow when condition is met and proceed with test.

6Advantages over Thread.sleep()

snapshotFlow has three key advantages: Speed - proceeds immediately when condition is met, no fixed wait time. Reliability - hooks directly into Snapshot system. Consistency - observes data rather than guessing transition times. Makes your tests faster and more reliable.

7When to use it?

snapshotFlow is ideal for testing states inaccessible to Semantics Tree. If state is already reflected in Semantics (like button enabled state), use standard Matchers. Indispensable for testing complex synchronization, especially animation or scroll-based interactions.

8Importance for professional codebase

snapshotFlow bridges Compose's reactive world with tests' imperative world. Lets you test not just what user sees but how application behaves. Helps build more robust and professional codebase. A must-have tool in Android developers' testing strategies.

Original article: ProAndroidDev - Medium — Jetpack Compose'da snapshotFlow ile İç Durum Testleri