Jetpack Compose
Android's modern declarative UI toolkit. 60% of top 1,000 Play Store apps now use Compose.
Key Principles
- State Hoisting - Lift state to lowest common ancestor. Composables should be stateless.
- Unidirectional Data Flow - State down, events up via callbacks.
- Three-Phase Rendering - Composition (what) > Layout (where) > Drawing (how). Skip phases with lambda modifiers.
- Type Stability - Use
@Immutable/@Stableannotations for smart recomposition skipping.
Essential Patterns
// State hoisting + lifecycle-aware collection
@Composable
fun LoginScreen(viewModel: LoginViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
LoginContent(
email = uiState.email,
onEmailChange = viewModel::updateEmail,
onLogin = viewModel::login
)
}
// Deferred state reads for performance
@Composable
fun Title(scrollProvider: () -> Int) {
Column(modifier = Modifier.offset {
IntOffset(x = 0, y = scrollProvider()) // Layout phase only
}) { /* ... */ }
}
2025 Features
- Pausable Composition - Split work across frames, eliminating jank
- Background Text Prefetch - Pre-warm text layout caches on background threads
Material Design 3
Dynamic color, tonal elevation, and M3 Expressive updates (MDC-Android 1.14.0+).
- Use
dynamicDarkColorScheme()/dynamicLightColorScheme()on Android 12+ - Always provide fallback color schemes for pre-Android 12
- Adaptive navigation: NavigationBar (compact) / NavigationRail (medium) / PermanentDrawer (expanded)
Animations
- AnimatedVisibility - Show/hide with enter/exit transitions
- animateContentSize() - Smooth size changes
- AnimatedContent - State-based content transitions
- spring() - Physics-based natural motion (preferred over tween)
- Use
Modifier.graphicsLayer{}for GPU-accelerated draw-phase-only animations
MVVM + Clean Architecture
Three-layer separation: UI (Compose) > Domain (Use Cases) > Data (Repository + DataSource).
// Sealed class for UI state
sealed interface ProfileUiState {
data object Loading : ProfileUiState
data class Success(val profile: UserProfile) : ProfileUiState
data class Error(val message: String) : ProfileUiState
}
// ViewModel with StateFlow
class ProfileViewModel(
private val getProfile: GetUserProfileUseCase,
savedStateHandle: SavedStateHandle
) : ViewModel() {
private val userId = savedStateHandle.get<String>("userId")!!
val uiState: StateFlow<ProfileUiState> = getProfile(userId)
.map { ProfileUiState.Success(it) }
.catch { emit(ProfileUiState.Error(it.message ?: "Unknown error")) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), ProfileUiState.Loading)
}
Coroutines & Flow
- StateFlow - Hot flow for UI state (always has value, conflated)
- SharedFlow - Hot flow for events (no initial value, configurable replay)
- collectAsStateWithLifecycle() - Lifecycle-aware collection in Compose
- stateIn() - Convert cold Flow to StateFlow with WhileSubscribed strategy
- Use
viewModelScopefor ViewModel coroutines,lifecycleScopefor UI
Hilt Dependency Injection
@HiltAndroidAppon Application class@AndroidEntryPointon Activities/Fragments@HiltViewModel+@Inject constructorfor ViewModels@Bindsfor interface bindings,@Providesfor third-party objects- Scopes:
@Singleton,@ViewModelScoped,@ActivityScoped
Performance Optimization
Priority Checklist
1) Baseline Profiles (30-50% startup improvement), 2) R8 Full Mode, 3) Lazy initialization, 4) Image optimization with Coil/Glide, 5) LazyColumn with stable keys.
- Baseline Profiles - Pre-compile critical user journeys. Google Maps: 30% faster startup.
- R8 Full Mode - Maximum code shrinking. Disney+: 30% faster startup, 25% fewer ANRs.
- App Startup Library - Manage initialization order and defer non-critical init
- Target under 500ms TTID (Time to Initial Display)
- LeakCanary - Always add to debug builds for memory leak detection
Data & Security
- Room - SQLite with type-safe queries, Flow support, migrations
- Retrofit + OkHttp - HTTP networking with interceptors and caching
- Security - Use Tink for encryption (EncryptedSharedPreferences deprecated), Android Keystore for keys
- WorkManager - Guaranteed background work with constraints
- Offline-First - Room as single source of truth, sync with network
- Paging 3 - Efficient large dataset loading with PagingSource and RemoteMediator
Testing
Testing pyramid: 70% unit, 20% integration, 10% UI tests.
- MockK - Kotlin-first mocking with coroutine support
- Turbine - Flow testing made simple
- Compose Test - Semantic-based UI testing with createComposeRule
- Robolectric - Fast Android tests without emulator
- Paparazzi/Roborazzi - Screenshot testing for UI regression