# Android Performance

> Android performance best practices for this project. Covers Compose stability and recomposition reduction, LazyList optimization with stable keys and contentType, image loading with Coil, Paging3 caching with cachedIn, memory management avoiding Context in ViewModels, and ProGuard rules for Moshi and Konvert.

- Skill: `thetruong1099/android-performance` (Agent Skill)
- Install (CLI): `npx skillmds@latest add thetruong1099/android-performance`
- Raw SKILL.md: https://api.skillmd.com/api/skills/thetruong1099/android-performance/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: thetruong1099 (https://skillmd.com/u/thetruong1099)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/thetruong1099/android-performance

---


# Android Performance

## Compose Performance

### Stability & Recomposition

```kotlin
// 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

```kotlin
val sortedItems = remember(items) { items.sortedBy { it.name } }  // cache expensive computation
val hasItems by remember { derivedStateOf { items.isNotEmpty() } }  // derived from other state
```

### LazyList Performance

```kotlin
// 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)

```kotlin
AsyncImage(
    model = ImageRequest.Builder(LocalContext.current)
        .data(item.imageUrl)
        .crossfade(true)
        .build(),
    contentDescription = item.name,
    modifier = Modifier.size(120.dp, 160.dp),
)
```

## Paging Performance

```kotlin
// ALWAYS cachedIn(viewModelScope) to survive configuration changes
val result = callPagingDataWithInternet(
    callFlow = { useCase() },
    onError = { 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

```kotlin
// 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

```kotlin
OkHttpClient.Builder()
    .cache(Cache(cacheDir, 10 * 1024 * 1024))  // 10MB cache
    .connectTimeout(30, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .build()
```

## ProGuard/R8 Rules

```proguard
# Moshi
-keep class com.template.data.**.dto.** { *; }
-keepclassmembers class * { @com.squareup.moshi.Json <fields>; }

# Konvert generated mappers
-keep class com.template.data.**.mapper.** { *; }
```

