Bu android eğitimi, Jetpack Compose derleyici raporları ve Perfetto ile gereksiz recomposition kaynaklarını bulmayı, immutable UI state tasarlamayı ve değişikliği ölçülebilir biçimde doğrulamayı anlatır.
Android Eğitimi: Jetpack Compose Kararlılık Raporlarıyla Recomposition Avı
Android eğitimi için başlangıç noktası: Compose kararlılık raporları
Bir android eğitimi veya üretim kod incelemesinde recomposition sorununu sadece Layout Inspector'daki sayaçtan teşhis etmeyin. Sayaç, bir composable'in yeniden çalıştığını gösterir; neden skip edilemediğini göstermez. Compose Compiler raporları ise parametrelerin stable, unstable veya runtime-stable olarak nasıl sınıflandığını ve composable'in skippable olup olmadığını modül bazında yazar. Kotlin 2.x tabanlı Compose compiler Gradle eklentisi kullanılıyorsa uygulama modülünde aşağıdaki hedefleri açın ve build sonrasında build/compose_compiler altındaki composables ve classes raporlarını sürüm kontrolü dışı bir CI artifact'i olarak saklayın.
composeCompiler {
reportsDestination = layout.buildDirectory.dir("compose_compiler")
metricsDestination = layout.buildDirectory.dir("compose_compiler")
stabilityConfigurationFile =
rootProject.layout.projectDirectory.file("compose_stability.conf")
}
Raporu okumada kritik ayrım şudur: Bir composable restartable olup skippable değilse, parent yeniden compose olduğunda gövdesi tekrar yürütülür. Bunun sık nedeni List, Map, third-party DTO veya mutable alan taşıyan bir parametredir. Örneğin classes raporunda `unstable class FeedUiState` görüp ekranda yalnızca liste değiştiğinde üst toolbar'in de çalıştığını doğrulayabilirsiniz. Bu çıktı, android studio eğitimi kapsamında kullanılan Compose Layout Inspector recomposition sayacıyla eşleştirildiğinde statik neden ile çalışma anındaki sonucu aynı ekranda doğrulama imkani verir.
stabilityConfigurationFile kısa vadeli bir kaçış mekanizması değil, denetlenmiş bir sözleşmedir. Aşağıdaki tanım derleyiciye ilgili türün instance'larının gözlemlenebilir biçimde değişmeyeceği vaadini verir. Bu türün içinde `var`, `MutableState`, değiştirilebilir koleksiyon veya dışarıdan mutasyona açık bir referans varsa derleyici artık değişimi göremeyeceğinden UI eski veriyle kalabilir. Bu nedenle yalnızca gerçekten immutable olan, sahipliği uygulamada bulunan API modellerini ekleyin.
# compose_stability.conf
com.example.core.model.ServerDrivenSection
com.example.core.model.ImmutableMoney
Jetpack Compose'da immutable UI state ve koleksiyon sınırı
android mvvm mimarisi içinde ViewModel'den ekrana `List<Article>` vermek teknik olarak çalışır, ancak Kotlin'in standart `List` arayüzü derleyicinin gözünde mutasyon garantisi taşımaz. Repository her emission'da yeni liste üretse bile Compose, bu garantiyi türden çıkaramaz. UI sınırında kotlinx.collections.immutable kullanmak hem veri sahipliğini açık eder hem de yapısal paylaşım sayesinde tek eleman değişiminde tüm listeyi kopyalama maliyetinden kaçınır. Aşağıdaki modelde `@Immutable` ancak tüm alanların bu sözleşmeyi gerçekten sağlaması nedeniyle doğrudur.
import androidx.compose.runtime.Immutable
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@Immutable
data class FeedUiState(
val items: ImmutableList<ArticleRow> = persistentListOf(),
val isRefreshing: Boolean = false,
val error: String? = null
)
@Immutable
data class ArticleRow(
val id: Long,
val title: String,
val liked: Boolean
)
Buradaki edge case `@Immutable` anotasyonunun runtime'da defensive copy üretmemesidir. Örneğin `data class FeedUiState(val items: List<ArticleRow>)` üzerine anotasyon eklemek, `items` referansını başka katmanın değiştirmesini engellemez; sadece compiler'a güvence beyan eder. Dönüşümün repository veya use case katmanında tek yerde yapılması daha güvenlidir: `networkItems.toPersistentList()`. Kotlin eğitimi sırasında özellikle Java interop'tan gelen `java.util.List` nesnelerini bu sınırdan geçirmeden UI state'e koymamak gerekir; Java kodu aynı listeyi sonradan mutate edebilir.
Her immutable dönüşüm de otomatik olarak kazanç değildir. Örneğin her recomposition içinde `items.toPersistentList()` çağırmak yeni persistent yapı üretip referans eşitliğini bozabilir. Dönüşümü Flow zincirinde state değiştiğinde yapın ve UI'ya hazır state verin. Bu ayrım, kotlin kursu projelerinde sık görülen `collectAsState()` sonucunu composable içinde tekrar map etme hatasını engeller.
val uiState: StateFlow<FeedUiState> = repository.observeArticles()
.map { articles ->
FeedUiState(items = articles.map(::toRow).toPersistentList())
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), FeedUiState())
Android MVVM mimarisi ile parametre ve lambda kimliğini daraltma
Bir ekranın tamamına ViewModel veya geniş bir `UiState` geçirmek, değişen alanla ilgisi olmayan child composable'ların parametre karşılaştırmasına girmesine yol açar. Bunun yerine route composable Flow toplar, screen composable ise ihtiyacı olan immutable değerleri alır. Aşağıdaki ayrımda `FeedToolbar`, `items` değiştiğinde yeniden compose edilebilir parent altında bulunsa bile `isRefreshing` ve `onRefresh` değişmemişse skip edilebilir. Bu, mobil uygulama geliştirme eğitimi içinde 'state hoisting' tanımından daha önemli olan somut sınırdır.
@Composable
fun FeedRoute(viewModel: FeedViewModel) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
val onRefresh = remember(viewModel) { { viewModel.refresh() } }
FeedScreen(
items = state.items,
refreshing = state.isRefreshing,
onRefresh = onRefresh
)
}
@Composable
private fun FeedScreen(
items: ImmutableList<ArticleRow>,
refreshing: Boolean,
onRefresh: () -> Unit
) {
FeedToolbar(refreshing = refreshing, onRefresh = onRefresh)
ArticleList(items = items)
}
Lambda ayrıntısı önemlidir: `onRefresh = { viewModel.refresh() }` ifadesi parent her çalıştığında yeni closure üretebilir. Modern compiler modlarında strong skipping bu tür lambda'ları hatırlayabilir, fakat bunu varsayım olarak almak yerine rapordan ve trace'ten doğrulayın. Callback'in bir capture'a bağlı olması gerekiyorsa `remember(id) { { viewModel.open(id) } }` kullanın; `remember` anahtarına id eklemezseniz eski id ile çalışan stale callback üretirsiniz. Bu hata özellikle LazyColumn satırlarında yanlış öğenin açılması şeklinde görünür.
Uzun yaşayan effect'lerde farklı bir problem vardır: callback kimliği kararlı kalsın diye `remember` kullanırken callback gövdesindeki güncel state'i kaybedebilirsiniz. Coroutine içinde en güncel lambda'yı çağırmak için `rememberUpdatedState` kullanın. Bu teknik, bir android kursu projesinde snackbar veya navigation effect'i ilk composable parametresini yakalayıp sonrakileri yok saydığında teşhis edilebilir.
@Composable
fun RetryEffect(retry: () -> Unit, events: Flow<Unit>) {
val currentRetry by rememberUpdatedState(retry)
LaunchedEffect(events) {
events.collect { currentRetry() }
}
}
Önce-sonra ölçümü: Macrobenchmark ve Perfetto ile doğrulama
Kararlılık raporu statik bir sinyaldir; değişikliğin kullanıcı akışındaki frame maliyetini Macrobenchmark ile ölçün. Aynı fiziksel veya emülatör cihazda, aynı release-benzeri variant ile en az 15 iterasyon çalıştırın; ilk açılış, ağ yanıtı ve animasyon gibi değişkenleri senaryodan çıkarın. Aşağıdaki test değişiklikten önce ve immutable state dönüşümünden sonra ayrı JSON sonuçları üretir. `FrameTimingMetric` içindeki P50, P90 ve P95 değerlerini karşılaştırın; yalnızca ortalama frame süresine bakmak, nadir ama görünür takılmaları gizler.
@RunWith(AndroidJUnit4::class)
class FeedBenchmark {
@get:Rule
val benchmarkRule = MacrobenchmarkRule()
@Test
fun scrollFeed() = benchmarkRule.measureRepeated(
packageName = "com.example.app",
metrics = listOf(FrameTimingMetric()),
iterations = 15,
startupMode = StartupMode.WARM,
setupBlock = { pressHome() }
) {
startActivityAndWait()
device.findObject(By.res("feed_list"))
.fling(Direction.DOWN)
}
}
Ölçümde P95 kötüleşirse Android Studio System Trace veya Perfetto arayüzünde aynı akışı kaydedin. Compose runtime tracing etkin olan debug/profileable build'de `compose:recompose`, `compose:measure` ve `compose:layout` slice'larını UI thread üzerinde arayın. Immutable koleksiyona geçişten sonra hedef, sadece recomposition sayısının düşmesi değildir: trace'te ilgili frame içindeki composition slice'ının daha az çalışması ve `Choreographer#doFrame` süresinin 16.6 ms veya cihazın yenileme bütçesi altında kalmasıdır. Recomposition sayısı azalırken `toPersistentList()` büyük listelerde ana thread'e taşındıysa toplam frame süresi artabilir; bu nedenle iki aracı birlikte kullanmak gerekir.
Adil bir önce-sonra karşılaştırmasında benchmark APK'sının derleme modunu sabit tutun ve iki koşulda da aynı başlangıç verisini kullanın. Baseline Profile, R8 kuralları veya debug logları iki deney arasında değişirse sonuç Compose değişikliğine ait olmaz. Ölçüm çıktısını CI'da saklamak için `./gradlew :macrobenchmark:connectedCheck` komutundan sonra benchmark JSON dosyasını artifact olarak yükleyin; PR açıklamasına örneğin `scrollFeed P95: 21.4 ms -> 15.8 ms` gibi aynı cihazdan gelen somut farkı ekleyin.
Play Store yayınlama öncesi regression kapısı ve rapor denetimi
play store yayınlama aşamasında compiler raporunu bir kalite kapısına dönüştürmek için her unstable satırı build'i kırmayın. Bazı Android framework, navigation veya üçüncü taraf türleri kaçınılmaz biçimde unstable olabilir. Bunun yerine kritik ekranların composable raporlarını baseline'a karşı diff edin ve yeni eklenen public screen parametrelerinde `List`, `Map`, `Any`, mutable DTO veya ViewModel geçişini kod inceleme kuralı yapın. CI'da raporu görünür kılmak için aşağıdaki komut, üretilen sınıf raporlarını tek dosyada toplar.
./gradlew :app:assembleRelease
find app/build/compose_compiler -name '*-classes.txt' -print0 | xargs -0 cat > build/compose-stability-report.txt
Sadece release build'i inceleyin demek de yeterli değildir: debug ve release classpath'leri farklı serializer, analytics veya DI binding'leri getirebilir ve UI modelinin gerçek türünü değiştirebilir. Bu nedenle android eğitim materyalindeki örnek uygulamada olduğu gibi compiler raporunu release-benzeri variant'tan üretin, Macrobenchmark hedef APK'sını aynı variant'a bağlayın ve Play Console'a gönderilecek App Bundle ile benchmark APK'sının kaynak commit'ini kaydedin. Böylece bir regression görüldüğünde commit, statik kararlılık raporu ve Perfetto trace'i aynı inceleme zincirinde kalır.
İlgili Eğitim
YTÜSEM İlgili Eğitim
Sık Sorulan Sorular
Jetpack Compose için android studio eğitimi sırasında stability report nasıl açılır?
Kotlin 2.x Compose compiler eklentisinde `composeCompiler { reportsDestination = ...; metricsDestination = ... }` yapılandırmasını ilgili Android modülüne ekleyin ve `assembleRelease` çalıştırın. build/compose_compiler altındaki `*-classes.txt` dosyasında unstable türleri, `*-composables.txt` dosyasında ise composable'in skippable niteliğini inceleyin.
Kotlin eğitimi projelerinde @Immutable eklemek List parametresini stable yapar mı?
Hayır. `@Immutable`, Compose'a verilen bir sözdür ve mutable `List` nesnesini immutable hale getirmez. UI state sınırında `kotlinx.collections.immutable.ImmutableList` kullanın, dönüşümü Flow zincirinde `toPersistentList()` ile bir kez yapın ve kaynak listenin sonradan mutate edilmediğini garanti edin.
Android MVVM mimarisi kullanırken gereksiz recomposition nasıl ölçülür?
Önce Compose Compiler raporuyla unstable parametreyi bulun. Sonra aynı kullanıcı akışını `MacrobenchmarkRule` ve `FrameTimingMetric` ile en az 15 iterasyon çalıştırın. Sonuçtaki P95 frame süresini önce-sonra karşılaştırın; Android Studio System Trace veya Perfetto'da `compose:recompose` slice'ları ile maliyetin hangi composable altında oluştuğunu doğrulayın.
Mobil uygulama geliştirme eğitimi için lambda recomposition hatası nasıl önlenir?
Callback her parent recomposition'ında yeni closure üretiyorsa `remember(stableKey) { { ... } }` kullanın. Closure bir coroutine veya effect içinde güncel callback'i çağıracaksa `rememberUpdatedState` ekleyin. `remember` anahtarına değişen id'yi koymayı unutmak, eski öğeye işlem yapan stale callback üretir.
AI / LLM Discovery
Bu makale Opendart Akademi Android eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.


