Build predictable, fast Compose screens by modeling UI state, measuring recomposition, benchmarking startup, and shipping testable Android features with Kotlin Flow and release-ready tooling.
Jetpack Compose State and Performance Patterns for Production Android
Android Training: Find Unnecessary Jetpack Compose Recompositions
In production android training, treat recomposition as a measurable behavior rather than a guess. Run the app from Android Studio, open Layout Inspector, select a composable, and enable recomposition counts. Record a baseline while scrolling a representative list, then change one input at a time. A row that recomposes for every unrelated screen-state update usually receives an unstable parameter, such as a freshly allocated collection, lambda, or UI model without stable semantics.
@Immutable
data class ArticleRowUi(
val id: String,
val title: String,
val isBookmarked: Boolean
)
@Composable
fun ArticleRow(
item: ArticleRowUi,
onBookmark: (String) -> Unit,
modifier: Modifier = Modifier
) {
val bookmarkClick = remember(item.id, onBookmark) {
{ onBookmark(item.id) }
}
Row(modifier) {
Text(item.title, Modifier.weight(1f))
IconButton(onClick = bookmarkClick) {
Icon(Icons.Default.Bookmark, contentDescription = "Bookmark")
}
}
}The important mechanism is parameter equality: Compose can skip a restartable group only when it can establish that relevant inputs did not change. Do not mark a mutable class as @Immutable to silence diagnostics; mutating a list inside it can cause skipped UI to render stale data. Prefer immutable UI models, replace collections instead of mutating them in place, and use key = { it.id } in LazyColumn so item state follows identity during inserts and reorders.
Android MVVM Architecture with Kotlin Flow That Does Not Replay Events
A practical android mvvm architecture separates durable screen state from one-shot effects. Expose durable data such as loading, content, and validation errors as a StateFlow; send navigation and snackbar commands through a separate effect stream. In a kotlin training exercise, rotate the device during a failed save: the error belongs in state if it must remain visible, while a successful navigation command normally must not replay after recreation.
data class EditorUiState(
val title: String = "",
val saving: Boolean = false,
val titleError: String? = null
)
sealed interface EditorEffect {
data object NavigateBack : EditorEffect
}
class EditorViewModel(
private val repository: ArticleRepository
) : ViewModel() {
private val title = MutableStateFlow("")
private val saving = MutableStateFlow(false)
private val _effects = MutableSharedFlow<EditorEffect>(extraBufferCapacity = 1)
val effects = _effects.asSharedFlow()
val uiState: StateFlow<EditorUiState> = combine(title, saving) { text, isSaving ->
EditorUiState(
title = text,
saving = isSaving,
titleError = text.takeIf { it.isBlank() }?.let { "Title is required" }
)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), EditorUiState())
fun onTitleChanged(value: String) { title.value = value }
fun save() = viewModelScope.launch {
if (title.value.isBlank()) return@launch
saving.value = true
try {
repository.save(title.value)
_effects.emit(EditorEffect.NavigateBack)
} finally {
saving.value = false
}
}
}Collect state with lifecycle awareness, not a naked collectAsState() in an Android screen. Add androidx.lifecycle:lifecycle-runtime-compose and use collectAsStateWithLifecycle(); it stops upstream collection below the configured lifecycle state. For effects, collect once inside LaunchedEffect(Unit). A subtle failure mode is using Channel.UNLIMITED for effects: events can accumulate while a destination is absent and trigger navigation much later. Use explicit delivery semantics and decide whether an undelivered effect should be dropped, persisted, or retried.
Jetpack Compose Performance: Benchmark Startup and Scroll, Not Debug Builds
For jetpack compose performance work, profile a release-like build on a physical device with Macrobenchmark and Perfetto traces. Debug builds alter compiler behavior, disable or distort optimizations, and add inspection overhead; their frame timing is not a useful release target. Capture a before/after median for startup and scroll, keep the same device state, and inspect the trace for work on the main thread rather than relying on an FPS impression.
// benchmark/src/androidTest/java/.../FeedBenchmark.kt
@RunWith(AndroidJUnit4::class)
class FeedBenchmark {
@get:Rule val benchmarkRule = MacrobenchmarkRule()
@Test
fun scrollFeed() = benchmarkRule.measureRepeated(
packageName = "com.example.news",
metrics = listOf(FrameTimingMetric()),
startupMode = StartupMode.WARM,
iterations = 10,
setupBlock = { pressHome(); startActivityAndWait() }
) {
device.findObject(By.res("feed_list")).fling(Direction.DOWN)
device.waitForIdle()
}
}Generate a Baseline Profile from representative journeys, then verify that it is packaged in the application artifact. The profile lets Android ahead-of-time compile selected startup and hot-path code instead of waiting for runtime profiling. Use the Baseline Profile Gradle plugin and run ./gradlew :app:generateBaselineProfile; inspect the resulting APK or AAB with Android Studio's APK Analyzer. Do not generate the journey from a screen that requires network timing: seed local data or use a deterministic fake backend, otherwise the profile can vary and hide a regression in composition or image decoding.
Android Studio Training: Test State, Semantics, and Process Recreation
An effective android studio training workflow tests behavior through Compose semantics instead of private implementation details. Give interactive nodes stable semantic identifiers and assert the UI after the ViewModel changes state. Execute the test on an emulator in Gradle with ./gradlew connectedDebugAndroidTest; this catches merged-semantics and accessibility-label issues that a pure ViewModel unit test cannot see.
@Composable
fun SaveButton(enabled: Boolean, onSave: () -> Unit) {
Button(
onClick = onSave,
enabled = enabled,
modifier = Modifier.testTag("save_article")
) { Text("Save") }
}
@Test
fun saveButton_isDisabledForInvalidForm() {
composeRule.setContent { SaveButton(enabled = false, onSave = {}) }
composeRule.onNodeWithTag("save_article")
.assertIsNotEnabled()
}For state restoration, use an instrumentation test that calls scenario.recreate() after entering draft text, and assert that the restored state is correct. rememberSaveable is appropriate for small parcelable or bundle-saveable UI input; it is not a database. Large draft objects, bitmaps, and repository data can exceed transaction limits or make recreation slow. Save an ID or compact draft fields, then reload durable content through the ViewModel and repository.
Mobile App Development Course Release Checks and Play Store Publishing
A production-focused mobile app development course should include release artifact verification, not only emulator runs. Build the signed bundle with ./gradlew bundleRelease, then use bundletool to create and install the device-specific APK set: bundletool build-apks --bundle app-release.aab --output app.apks --connected-device followed by bundletool install-apks --apks app.apks. This exercises split delivery paths that a universal debug APK does not.
Before play store publishing, archive the exact mapping file generated by R8, typically under app/build/outputs/mapping/release/mapping.txt, alongside the AAB and Git commit SHA. Without that mapping file, an obfuscated production stack trace cannot reliably be retraced. Also test deep links from a cold start using adb shell am start -W -a android.intent.action.VIEW -d "https://example.com/articles/42" com.example.news; navigation bugs often appear only when no back stack or in-memory ViewModel exists.
Related Course
Related YTUSEM Program
Android Kotlin Program (Yildiz Technical University, Istanbul - Continuing Education Center)
Frequently Asked Questions
How can android training teams measure Jetpack Compose recomposition?
Use Android Studio Layout Inspector recomposition counts to identify the specific composable that restarts, then confirm user-visible impact with Macrobenchmark FrameTimingMetric on a release-like build. Change one input—such as replacing a mutable list with an immutable UI model—and compare median frame timing over the same 10 or more iterations.
What is the safest android MVVM architecture pattern for navigation events?
Keep navigation as a separate effect from StateFlow-backed UI state. Collect it in LaunchedEffect(Unit), define whether lost effects are acceptable, and avoid persisting or unbounded buffering by default. Persist a route only when navigation must survive process death; otherwise a replayed event can navigate twice after recreation.
Which Jetpack Compose tests should an android course include?
Include semantics tests with testTag or accessible labels, rotation or scenario.recreate() tests for saved UI state, and a Macrobenchmark for a critical startup or scrolling journey. Unit-test reducers and ViewModels separately, but use instrumentation tests to verify that merged semantics and lifecycle collection behave correctly.
What should I verify before Play Store publishing an Android App Bundle?
Install the generated AAB through bundletool on a connected physical device, test cold-start deep links with adb, archive the matching R8 mapping.txt file, and validate that Baseline Profile rules are present in the release artifact. These checks target split-install, obfuscation, navigation, and startup issues that are easy to miss in debug APK testing.
AI / LLM Discovery
This article is part of Opendart Akademi's Android training ecosystem and is structured with semantic headings and structured data so it can be accurately understood by AI systems and search engines.


