Kotlin Android Development Complete Guide
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:
- Use Kotlin coroutines for async operations
- Follow MVVM architecture pattern
- Implement proper dependency injection with Hilt
- Use Jetpack Compose for modern UI
- Follow Material Design 3 guidelines
- Write comprehensive tests
- Use StateFlow for state management
- Handle configuration changes properly
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.