← Back to Guides

Kotlin Android Development Complete Guide

📖 19 min read | 📅 January 2025 | 🏷️ Mobile Development

Introduction

Kotlin is the modern, official language for Android development. Combined with Jetpack Compose and Material Design 3, it enables rapid development of beautiful, performant native Android applications.

1. Kotlin Fundamentals

// Variables
val immutable = "Cannot change"
var mutable = "Can change"

// Null safety
var nullable: String? = null
val length = nullable?.length ?: 0

// Data classes
data class User(
    val id: Int,
    val name: String,
    val email: String
)

// Extension functions
fun String.isEmail(): Boolean {
    return this.contains("@")
}

// Lambda expressions
val sum = { a: Int, b: Int -> a + b }
list.filter { it > 5 }.map { it * 2 }

// When expression
when (x) {
    1 -> print("One")
    2, 3 -> print("Two or Three")
    in 4..10 -> print("Between 4 and 10")
    else -> print("Other")
}

// Coroutines
suspend fun fetchData(): String {
    delay(1000)
    return "Data"
}

lifecycleScope.launch {
    val result = fetchData()
    println(result)
}

2. Project Setup

// build.gradle.kts (app level)
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}

android {
    namespace = "com.example.myapp"
    compileSdk = 34
    
    defaultConfig {
        applicationId = "com.example.myapp"
        minSdk = 24
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"
    }
    
    buildFeatures {
        compose = true
    }
    
    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.3"
    }
}

dependencies {
    implementation("androidx.core:core-ktx:1.12.0")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
    implementation("androidx.activity:activity-compose:1.8.2")
    implementation(platform("androidx.compose:compose-bom:2024.01.00"))
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.material3:material3")
}

3. Jetpack Compose Basics

import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun MyScreen() {
    var count by remember { mutableStateOf(0) }
    
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        Text(
            text = "Counter: $count",
            style = MaterialTheme.typography.headlineMedium
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        Button(onClick = { count++ }) {
            Text("Increment")
        }
        
        OutlinedButton(onClick = { count-- }) {
            Text("Decrement")
        }
    }
}

// Lazy lists
@Composable
fun UserList(users: List<User>) {
    LazyColumn {
        items(users) { user ->
            UserCard(user)
        }
    }
}

@Composable
fun UserCard(user: User) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(8.dp)
    ) {
        Row(
            modifier = Modifier.padding(16.dp)
        ) {
            Text(user.name, style = MaterialTheme.typography.bodyLarge)
        }
    }
}

4. State Management

// ViewModel
class MainViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()
    
    fun loadData() {
        viewModelScope.launch {
            _uiState.update { it.copy(loading = true) }
            try {
                val data = repository.getData()
                _uiState.update { 
                    it.copy(loading = false, data = data) 
                }
            } catch (e: Exception) {
                _uiState.update { 
                    it.copy(loading = false, error = e.message) 
                }
            }
        }
    }
}

data class UiState(
    val loading: Boolean = false,
    val data: List<String> = emptyList(),
    val error: String? = null
)

// Usage in Compose
@Composable
fun MyScreen(viewModel: MainViewModel = viewModel()) {
    val uiState by viewModel.uiState.collectAsState()
    
    when {
        uiState.loading -> CircularProgressIndicator()
        uiState.error != null -> Text("Error: ${uiState.error}")
        else -> DataList(uiState.data)
    }
}

5. Navigation

// build.gradle.kts
implementation("androidx.navigation:navigation-compose:2.7.6")

// Navigation setup
@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = "home"
    ) {
        composable("home") {
            HomeScreen(
                onNavigateToDetails = { id ->
                    navController.navigate("details/$id")
                }
            )
        }
        composable(
            route = "details/{userId}",
            arguments = listOf(
                navArgument("userId") { type = NavType.IntType }
            )
        ) { backStackEntry ->
            val userId = backStackEntry.arguments?.getInt("userId")
            DetailsScreen(
                userId = userId,
                onBack = { navController.popBackStack() }
            )
        }
    }
}

6. Networking with Retrofit

// build.gradle.kts
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")

// API interface
interface ApiService {
    @GET("users")
    suspend fun getUsers(): List<User>
    
    @GET("users/{id}")
    suspend fun getUser(@Path("id") id: Int): User
    
    @POST("users")
    suspend fun createUser(@Body user: User): User
    
    @PUT("users/{id}")
    suspend fun updateUser(
        @Path("id") id: Int, 
        @Body user: User
    ): User
}

// Retrofit setup
object RetrofitInstance {
    private const val BASE_URL = "https://api.example.com/"
    
    private val client = OkHttpClient.Builder()
        .addInterceptor(HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BODY
        })
        .build()
    
    val api: ApiService by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
}

// Repository
class UserRepository {
    suspend fun getUsers(): Result<List<User>> {
        return try {
            Result.success(RetrofitInstance.api.getUsers())
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

7. Local Database with Room

// build.gradle.kts
implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")
ksp("androidx.room:room-compiler:2.6.1")

// Entity
@Entity(tableName = "users")
data class UserEntity(
    @PrimaryKey val id: Int,
    @ColumnInfo(name = "name") val name: String,
    @ColumnInfo(name = "email") val email: String
)

// DAO
@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun getAllUsers(): Flow<List<UserEntity>>
    
    @Query("SELECT * FROM users WHERE id = :id")
    suspend fun getUserById(id: Int): UserEntity?
    
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertUser(user: UserEntity)
    
    @Delete
    suspend fun deleteUser(user: UserEntity)
}

// Database
@Database(entities = [UserEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
    
    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null
        
        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "app_database"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

8. Dependency Injection with Hilt

// build.gradle.kts (project)
plugins {
    id("com.google.dagger.hilt.android") version "2.48" apply false
}

// build.gradle.kts (app)
plugins {
    id("com.google.dagger.hilt.android")
    id("com.google.devtools.ksp")
}

dependencies {
    implementation("com.google.dagger:hilt-android:2.48")
    ksp("com.google.dagger:hilt-android-compiler:2.48")
    implementation("androidx.hilt:hilt-navigation-compose:1.1.0")
}

// Application class
@HiltAndroidApp
class MyApplication : Application()

// Module
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Provides
    @Singleton
    fun provideApiService(): ApiService {
        return RetrofitInstance.api
    }
    
    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
        return AppDatabase.getDatabase(context)
    }
}

// ViewModel with injection
@HiltViewModel
class MainViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel() {
    // ViewModel code
}

// Activity
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp()
        }
    }
}

9. Material Design 3

@Composable
fun MyApp() {
    val colorScheme = dynamicColorScheme()
    
    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = {
            Surface(
                modifier = Modifier.fillMaxSize(),
                color = MaterialTheme.colorScheme.background
            ) {
                AppNavigation()
            }
        }
    )
}

// Components
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopAppBarExample() {
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("My App") },
                actions = {
                    IconButton(onClick = { }) {
                        Icon(Icons.Default.Settings, "Settings")
                    }
                }
            )
        }
    ) { paddingValues ->
        Content(Modifier.padding(paddingValues))
    }
}

// Cards and Dialogs
@Composable
fun CardExample() {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        elevation = CardDefaults.cardElevation(4.dp)
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text("Card Title", style = MaterialTheme.typography.titleLarge)
            Text("Card content goes here")
        }
    }
}

10. Testing

// Unit test
class UserViewModelTest {
    @Test
    fun `loadUsers updates state correctly`() = runTest {
        val viewModel = MainViewModel(fakeRepository)
        viewModel.loadUsers()
        
        val state = viewModel.uiState.value
        assertThat(state.loading).isFalse()
        assertThat(state.users).isNotEmpty()
    }
}

// Compose UI test
@get:Rule
val composeTestRule = createComposeRule()

@Test
fun myTest() {
    composeTestRule.setContent {
        MyScreen()
    }
    
    composeTestRule.onNodeWithText("Counter: 0").assertExists()
    composeTestRule.onNodeWithText("Increment").performClick()
    composeTestRule.onNodeWithText("Counter: 1").assertExists()
}
💡 Best Practices:

Conclusion

Kotlin and Jetpack Compose provide a modern, efficient way to build Android applications. Master these tools to create professional, maintainable Android apps with excellent user experiences.