Android Performance
Compose Performance
Stability & Recomposition
// Mark classes as @Immutable when passed to Composable
@Immutable
data class ItemUiModel(val id: String, val name: String, val coverUrl: String)
// Lambda parameters are stable - prefer over direct ViewModel access
@Composable fun ItemCard(onItemClick: () -> Unit) // GOOD: stable
@Composable fun ItemCard(viewModel: SampleViewModel) // BAD: unstable
remember & derivedStateOf
val sortedItems = remember(items) { items.sortedBy { it.name } } // cache expensive computation
val hasItems by remember { derivedStateOf { items.isNotEmpty() } } // derived from other state
LazyList Performance
// Always provide stable keys
LazyColumn {
items(items, key = { it.id }) { item -> ItemComponent(item) }
}
// contentType for heterogeneous lists
LazyColumn {
items(items, key = { it.id }, contentType = { it.type }) { item ->
when (item) {
is Header -> HeaderComponent(item)
is Item -> ItemComponent(item)
}
}
}
Image Loading (Coil)
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(item.imageUrl)
.crossfade(true)
.build(),
contentDescription = item.name,
modifier = Modifier.size(120.dp, 160.dp),
)
Paging Performance
// ALWAYS cachedIn(viewModelScope) to survive configuration changes
val result = callPagingDataWithInternet(
callFlow = { useCase() },
showErrorToast(it) },
).cachedIn(viewModelScope) // Essential!
// PagingConfig tuning
PagingConfig(
pageSize = 20,
prefetchDistance = 5, // How far ahead to prefetch
enablePlaceholders = false, // false for network-only
initialLoadSize = 40, // First page size
)
Memory Management
// WRONG: Context in ViewModel = memory leak
class MyViewModel(private val context: Context)
// RIGHT: ApplicationContext via Hilt
@HiltViewModel
class MyViewModel @Inject constructor(
@ApplicationContext private val appContext: Context
) : BaseViewModel<...>()
// Collect flows lifecycle-aware
val state by viewModel.uiState.collectAsStateWithLifecycle()
Network Performance
OkHttpClient.Builder()
.cache(Cache(cacheDir, 10 * 1024 * 1024)) // 10MB cache
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
ProGuard/R8 Rules
# Moshi
-keep class com.template.data.**.dto.** { *; }
-keepclassmembers class * { @com.squareup.moshi.Json <fields>; }
# Konvert generated mappers
-keep class com.template.data.**.mapper.** { *; }