# Android Native Development Complete guide to Android development with Kotlin and Jetpack Compose (2024-2025). ## Kotlin 2.1 Overview ### Key Features - **Null safety**: No more NullPointerExceptions - **Coroutines**: Structured concurrency - **Extension functions**: Extend classes without inheritance - **Sealed classes**: Type-safe state management - **Data classes**: Automatic equals/hashCode/toString ### Modern Kotlin Patterns **Coroutines:** ```kotlin // Suspend function suspend fun fetchUser(id: String): User { return withContext(Dispatchers.IO) { api.getUser(id) } } // Usage in ViewModel viewModelScope.launch { try { val user = fetchUser("123") _uiState.update { it.copy(user = user) } } catch (e: Exception) { _uiState.update { it.copy(error = e.message) } } } ``` **Flow (Reactive streams):** ```kotlin class UserRepository { fun observeUsers(): Flow> = flow { while (true) { emit(database.getUsers()) delay(5000) // Poll every 5 seconds } }.flowOn(Dispatchers.IO) } // Collect in ViewModel init { viewModelScope.launch { repository.observeUsers().collect { users -> _uiState.update { it.copy(users = users) } } } } ``` **Sealed classes (Type-safe states):** ```kotlin sealed class UiState { object Loading : UiState() data class Success(val data: List) : UiState() data class Error(val message: String) : UiState() } // Pattern matching when (uiState) { is UiState.Loading -> ShowLoader() is UiState.Success -> ShowData(uiState.data) is UiState.Error -> ShowError(uiState.message) } ``` ## Jetpack Compose ### Why Compose? - **Declarative**: Describe UI state, not imperative commands - **60% adoption**: In top 1,000 apps (2024) - **Less code**: 40% reduction vs Views - **Modern**: Built for Kotlin and coroutines - **Material 3**: First-class support ### Compose Basics ```kotlin @Composable fun UserListScreen(viewModel: UserViewModel = viewModel()) { val uiState by viewModel.uiState.collectAsState() Column(modifier = Modifier.fillMaxSize()) { when (val state = uiState) { is UiState.Loading -> { CircularProgressIndicator( modifier = Modifier.align(Alignment.CenterHorizontally) ) } is UiState.Success -> { LazyColumn { items(state.data) { user -> UserItem(user) } } } is UiState.Error -> { Text( text = state.message, color = MaterialTheme.colorScheme.error ) } } } } @Composable fun UserItem(user: User) { Row( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Text( text = user.name, style = MaterialTheme.typography.bodyLarge ) } } ``` **Key Composables:** - `Column/Row/Box`: Layout - `LazyColumn/LazyRow`: Recycler equivalent (virtualized) - `Text/Image/Icon`: Content - `Button/TextField`: Input - `Card/Surface`: Containers ## Architecture Patterns ### MVVM with Clean Architecture ```kotlin // Domain Layer - Use Case class GetUsersUseCase @Inject constructor( private val repository: UserRepository ) { operator fun invoke(): Flow>> = repository.getUsers() } // Data Layer - Repository interface UserRepository { fun getUsers(): Flow>> } class UserRepositoryImpl @Inject constructor( private val api: UserApi, private val dao: UserDao ) : UserRepository { override fun getUsers(): Flow>> = flow { // Local cache first val cachedUsers = dao.getUsers() emit(Result.success(cachedUsers)) // Then fetch from network try { val networkUsers = api.getUsers() dao.insertUsers(networkUsers) emit(Result.success(networkUsers)) } catch (e: Exception) { emit(Result.failure(e)) } }.flowOn(Dispatchers.IO) } // Presentation Layer - ViewModel @HiltViewModel class UserViewModel @Inject constructor( private val getUsersUseCase: GetUsersUseCase ) : ViewModel() { private val _uiState = MutableStateFlow(UserUiState()) val uiState: StateFlow = _uiState.asStateFlow() init { loadUsers() } private fun loadUsers() { viewModelScope.launch { getUsersUseCase().collect { result -> result.onSuccess { users -> _uiState.update { it.copy(users = users, isLoading = false) } }.onFailure { error -> _uiState.update { it.copy(error = error.message, isLoading = false) } } } } } } // UI State data class UserUiState( val users: List = emptyList(), val isLoading: Boolean = true, val error: String? = null ) ``` ### MVI (Model-View-Intent) **When to use:** - Unidirectional data flow needed - Complex state management - Time-travel debugging - Predictable state updates ```kotlin // State data class UserScreenState( val users: List = emptyList(), val isLoading: Boolean = false, val error: String? = null ) // Events (User intentions) sealed class UserEvent { object LoadUsers : UserEvent() data class DeleteUser(val id: String) : UserEvent() object RetryLoad : UserEvent() } // ViewModel class UserViewModel : ViewModel() { private val _state = MutableStateFlow(UserScreenState()) val state: StateFlow = _state.asStateFlow() fun onEvent(event: UserEvent) { when (event) { is UserEvent.LoadUsers -> loadUsers() is UserEvent.DeleteUser -> deleteUser(event.id) is UserEvent.RetryLoad -> loadUsers() } } } ``` ## Dependency Injection ### Hilt (Recommended for Large Apps) **Setup:** ```kotlin // App class @HiltAndroidApp class MyApplication : Application() // Activity @AndroidEntryPoint class MainActivity : ComponentActivity() // ViewModel @HiltViewModel class UserViewModel @Inject constructor( private val repository: UserRepository, private val analytics: Analytics ) : ViewModel() // Module @Module @InstallIn(SingletonComponent::class) object NetworkModule { @Provides @Singleton fun provideRetrofit(): Retrofit = Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build() @Provides @Singleton fun provideUserApi(retrofit: Retrofit): UserApi = retrofit.create(UserApi::class.java) } ``` ### Koin (Lightweight Alternative) **Setup:** ```kotlin // Module definition val appModule = module { single { UserRepository(get()) } viewModel { UserViewModel(get()) } } // Application class MyApp : Application() { override fun onCreate() { super.onCreate() startKoin { androidContext(this@MyApp) modules(appModule) } } } // Usage class UserViewModel( private val repository: UserRepository ) : ViewModel() ``` **Hilt vs Koin:** - **Hilt**: Compile-time, type-safe, Google-backed, complex setup - **Koin**: Runtime, simple DSL, 50% faster setup, reflection-based ## Performance Optimization ### R8 Optimization **Automatic optimizations:** - Code shrinking (remove unused) - Obfuscation (rename classes/methods) - Optimization (method inlining) ```groovy // build.gradle android { buildTypes { release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt') } } } ``` **Impact:** - 10-20% app size reduction - 20% faster startup - Harder to reverse engineer ### Baseline Profiles **Performance boost:** - 10-20% faster startup - Reduced jank in critical paths - AOT compilation of hot code ```gradle // build.gradle dependencies { implementation "androidx.profileinstaller:profileinstaller:1.3.1" } ``` ### Compose Performance **1. Stability annotations:** ```kotlin // Mark stable classes @Stable data class User(val name: String, val age: Int) // Immutable collections @Immutable data class UserList(val users: List) ``` **2. Avoid recomposition:** ```kotlin // ❌ Bad: Recomposes every render @Composable fun UserList(users: List) { LazyColumn { items(users) { user -> Text(user.name) // Recreated every time } } } // ✅ Good: Use keys @Composable fun UserList(users: List) { LazyColumn { items(users, key = { it.id }) { user -> Text(user.name) } } } ``` **3. Remember expensive computations:** ```kotlin @Composable fun ExpensiveList(items: List) { val sortedItems = remember(items) { items.sortedBy { it.priority } } LazyColumn { items(sortedItems) { item -> ItemCard(item) } } } ``` ## Testing ### Unit Testing (JUnit + MockK) ```kotlin class UserViewModelTest { private lateinit var viewModel: UserViewModel private val mockRepository = mockk() @Before fun setup() { viewModel = UserViewModel(mockRepository) } @Test fun `loadUsers should update state with users`() = runTest { // Given val users = listOf(User("1", "Test", "test@example.com")) coEvery { mockRepository.getUsers() } returns flowOf(Result.success(users)) // When viewModel.loadUsers() // Then val state = viewModel.uiState.value assertEquals(users, state.users) assertFalse(state.isLoading) } } ``` ### Compose Testing ```kotlin class UserListScreenTest { @get:Rule val composeTestRule = createComposeRule() @Test fun displayUsers() { val users = listOf(User("1", "John", "john@example.com")) composeTestRule.setContent { UserListScreen( users = users, onUserClick = {} ) } composeTestRule.onNodeWithText("John").assertIsDisplayed() } } ``` ### Instrumented Testing (Espresso) ```kotlin @RunWith(AndroidJUnit4::class) class LoginActivityTest { @get:Rule val activityRule = ActivityScenarioRule(LoginActivity::class.java) @Test fun loginFlow() { onView(withId(R.id.emailField)) .perform(typeText("test@example.com")) onView(withId(R.id.passwordField)) .perform(typeText("password123")) onView(withId(R.id.loginButton)) .perform(click()) onView(withText("Welcome")) .check(matches(isDisplayed())) } } ``` ## Material Design 3 ### Theme Setup ```kotlin @Composable fun AppTheme( darkTheme: Boolean = isSystemInDarkTheme(), dynamicColor: Boolean = true, content: @Composable () -> Unit ) { val colorScheme = when { dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { val context = LocalContext.current if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } darkTheme -> DarkColorScheme else -> LightColorScheme } MaterialTheme( colorScheme = colorScheme, typography = Typography, content = content ) } ``` ### Material Components ```kotlin // Cards Card( modifier = Modifier.fillMaxWidth(), elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) ) { Text("Content") } // FAB FloatingActionButton(onClick = { /* Do something */ }) { Icon(Icons.Default.Add, contentDescription = "Add") } // Navigation NavigationBar { items.forEach { item -> NavigationBarItem( icon = { Icon(item.icon, contentDescription = null) }, label = { Text(item.label) }, selected = selectedItem == item, onClick = { selectedItem = item } ) } } ``` ## Google Play Requirements (2024-2025) ### SDK Requirements - **Current**: Target Android 14 (API 34) - **Mandatory (Aug 31, 2025)**: Target Android 15 (API 35) ### Privacy & Security - **Privacy policy**: Required for apps collecting data - **Data safety**: Form in Play Console - **Permissions**: Request only needed, justify dangerous permissions - **Encryption**: HTTPS for network, KeyStore for sensitive data ### AAB (Android App Bundle) ```gradle android { bundle { density { enableSplit true } abi { enableSplit true } language { enableSplit true } } } ``` **Benefits:** - 15-30% smaller downloads - Dynamic feature modules - Instant apps support ## Common Pitfalls 1. **Main thread blocking**: Use coroutines with Dispatchers.IO 2. **Memory leaks**: Unregister listeners, cancel coroutines 3. **Configuration changes**: Use ViewModel, avoid Activity references 4. **Large images**: Use Coil/Glide for caching and resizing 5. **Forgetting permissions**: Runtime permission requests 6. **Ignoring Android versions**: Test on multiple API levels 7. **Not handling back press**: OnBackPressedDispatcher 8. **Hardcoded strings**: Use strings.xml for localization 9. **Not using Proguard/R8**: Enable in release builds 10. **Ignoring battery**: Use WorkManager for background tasks ## Resources **Official:** - Kotlin Docs: https://kotlinlang.org/docs/home.html - Compose Docs: https://developer.android.com/jetpack/compose - Material 3: https://m3.material.io/ - Android Guides: https://developer.android.com/guide **Community:** - Android Weekly: https://androidweekly.net/ - Kt.Academy: https://kt.academy/ - Coding in Flow: https://codinginflow.com/ - Philipp Lackner: https://pl-coding.com/