kotlin-development

Master Kotlin for Android and JVM development with coroutines, modern language features, and idiomatic patterns for building robust applications.

coppermare/skillverse1 installsMITSynced Aug 26

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI
---
name: kotlin-development
description: Master Kotlin for Android and JVM development with coroutines, modern language features, and idiomatic patterns for building robust applications.
license: MIT
---

# Kotlin Development

Master Kotlin's modern language features, coroutines, and idiomatic patterns for building robust Android and JVM applications with concise, expressive code.

## When to Use This Skill

- Building Android applications with Kotlin
- Developing JVM backend services
- Implementing coroutines for asynchronous programming
- Using Kotlin with Spring Boot or Ktor
- Writing Kotlin Multiplatform code
- Migrating Java codebases to Kotlin
- Leveraging Kotlin DSLs

## Core Concepts

### 1. Kotlin Language Fundamentals

**Modern Kotlin Syntax**

```kotlin
// Data classes - automatic equals, hashCode, toString, copy
data class User(
    val id: Long,
    val name: String,
    val email: String,
    val role: Role = Role.USER
)

enum class Role { USER, ADMIN, MODERATOR }

// Null safety
fun processUser(user: User?) {
    // Safe call operator
    val name = user?.name

    // Elvis operator
    val displayName = user?.name ?: "Anonymous"

    // Safe cast
    val admin = user as? Admin

    // Not-null assertion (use sparingly)
    val requiredUser = user!!

    // Let for null checks
    user?.let { u ->
        println("Processing ${u.name}")
    }
}

// Extension functions
fun String.toSlug(): String =
    this.lowercase()
        .replace(Regex("[^a-z0-9\\s-]"), "")
        .replace(Regex("\\s+"), "-")

// Scope functions
val user = User(1, "John", "john@example.com").apply {
    // Configure object, returns object
}

val result = user.run {
    // Access object, returns result
    "$name ($email)"
}

val length = user.let { it.name.length } // Transform, returns result

val formatted = with(user) {
    // Use object without it/this prefix
    "User: $name"
}
```

### 2. Sealed Classes and When Expressions

```kotlin
// Sealed classes for restricted hierarchies
sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val message: String, val cause: Throwable? = null) : Result<Nothing>()
    object Loading : Result<Nothing>()
}

// Exhaustive when expression
fun <T> handleResult(result: Result<T>): String = when (result) {
    is Result.Success -> "Success: ${result.data}"
    is Result.Error -> "Error: ${result.message}"
    Result.Loading -> "Loading..."
}

// Sealed interfaces (Kotlin 1.5+)
sealed interface UiState {
    object Initial : UiState
    object Loading : UiState
    data class Content(val items: List<Item>) : UiState
    data class Error(val message: String) : UiState
}
```

### 3. Coroutines Fundamentals

```kotlin
import kotlinx.coroutines.*

// Suspend functions
suspend fun fetchUser(id: Long): User {
    delay(1000) // Non-blocking delay
    return User(id, "John", "john@example.com")
}

// Launching coroutines
fun main() = runBlocking {
    // launch - fire and forget
    val job = launch {
        println("Hello from coroutine")
    }

    // async - returns Deferred with result
    val deferred = async {
        fetchUser(1)
    }
    val user = deferred.await()

    // Parallel execution
    val (user1, user2) = coroutineScope {
        val u1 = async { fetchUser(1) }
        val u2 = async { fetchUser(2) }
        Pair(u1.await(), u2.await())
    }
}

// Coroutine context and dispatchers
suspend fun processData() = withContext(Dispatchers.Default) {
    // CPU-intensive work
}

suspend fun fetchFromNetwork() = withContext(Dispatchers.IO) {
    // Network/IO operations
}

// On Android, use Dispatchers.Main for UI updates
```

## Essential Patterns

### Pattern 1: Repository Pattern with Coroutines

```kotlin
// Domain model
data class Article(
    val id: Long,
    val title: String,
    val content: String,
    val author: String,
    val publishedAt: Instant
)

// Repository interface
interface ArticleRepository {
    suspend fun getArticles(): List<Article>
    suspend fun getArticle(id: Long): Article?
    suspend fun saveArticle(article: Article): Article
    suspend fun deleteArticle(id: Long)
    fun observeArticles(): Flow<List<Article>>
}

// Implementation with caching
class ArticleRepositoryImpl(
    private val api: ArticleApi,
    private val dao: ArticleDao,
    private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) : ArticleRepository {

    override suspend fun getArticles(): List<Article> = withContext(dispatcher) {
        try {
            // Fetch from network
            val articles = api.getArticles()
            // Cache locally
            dao.insertAll(articles.map { it.toEntity() })
            articles
        } catch (e: Exception) {
            // Fallback to cache
            dao.getAll().map { it.toDomain() }
        }
    }

    override suspend fun getArticle(id: Long): Article? = withContext(dispatcher) {
        dao.getById(id)?.toDomain() ?: api.getArticle(id)?.also {
            dao.insert(it.toEntity())
        }
    }

    override fun observeArticles(): Flow<List<Article>> =
        dao.observeAll()
            .map { entities -> entities.map { it.toDomain() } }
            .flowOn(dispatcher)
}
```

### Pattern 2: ViewModel with StateFlow

```kotlin
import kotlinx.coroutines.flow.*

// UI State
data class ArticlesUiState(
    val articles: List<Article> = emptyList(),
    val isLoading: Boolean = false,
    val error: String? = null
)

// ViewModel
class ArticlesViewModel(
    private val repository: ArticleRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow(ArticlesUiState())
    val uiState: StateFlow<ArticlesUiState> = _uiState.asStateFlow()

    private val _events = MutableSharedFlow<UiEvent>()
    val events: SharedFlow<UiEvent> = _events.asSharedFlow()

    init {
        loadArticles()
        observeArticles()
    }

    fun loadArticles() {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true, error = null) }

            try {
                val articles = repository.getArticles()
                _uiState.update {
                    it.copy(articles = articles, isLoading = false)
                }
            } catch (e: Exception) {
                _uiState.update {
                    it.copy(error = e.message, isLoading = false)
                }
            }
        }
    }

    private fun observeArticles() {
        repository.observeArticles()
            .onEach { articles ->
                _uiState.update { it.copy(articles = articles) }
            }
            .launchIn(viewModelScope)
    }

    fun onArticleClicked(article: Article) {
        viewModelScope.launch {
            _events.emit(UiEvent.NavigateToDetail(article.id))
        }
    }

    sealed class UiEvent {
        data class NavigateToDetail(val articleId: Long) : UiEvent()
        data class ShowMessage(val message: String) : UiEvent()
    }
}
```

### Pattern 3: Jetpack Compose UI

```kotlin
import androidx.compose.runtime.*
import androidx.compose.foundation.lazy.*
import androidx.compose.material3.*

@Composable
fun ArticlesScreen(
    viewModel: ArticlesViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsState()

    LaunchedEffect(Unit) {
        viewModel.events.collect { event ->
            when (event) {
                is ArticlesViewModel.UiEvent.NavigateToDetail -> {
                    // Navigate to detail
                }
                is ArticlesViewModel.UiEvent.ShowMessage -> {
                    // Show snackbar
                }
            }
        }
    }

    ArticlesContent(
        uiState = uiState,
        onRefresh = viewModel::loadArticles,
        onArticleClick = viewModel::onArticleClicked
    )
}

@Composable
private fun ArticlesContent(
    uiState: ArticlesUiState,
    onRefresh: () -> Unit,
    onArticleClick: (Article) -> Unit
) {
    Scaffold(
        topBar = {
            TopAppBar(title = { Text("Articles") })
        }
    ) { paddingValues ->
        Box(
            modifier = Modifier
                .fillMaxSize()
                .padding(paddingValues)
        ) {
            when {
                uiState.isLoading && uiState.articles.isEmpty() -> {
                    CircularProgressIndicator(
                        modifier = Modifier.align(Alignment.Center)
                    )
                }
                uiState.error != null && uiState.articles.isEmpty() -> {
                    ErrorContent(
                        message = uiState.error,
                        onRetry = onRefresh,
                        modifier = Modifier.align(Alignment.Center)
                    )
                }
                else -> {
                    ArticlesList(
                        articles = uiState.articles,
                        onArticleClick = onArticleClick,
                        isRefreshing = uiState.isLoading,
                        onRefresh = onRefresh
                    )
                }
            }
        }
    }
}

@Composable
private fun ArticlesList(
    articles: List<Article>,
    onArticleClick: (Article) -> Unit,
    isRefreshing: Boolean,
    onRefresh: () -> Unit
) {
    SwipeRefresh(
        state = rememberSwipeRefreshState(isRefreshing),
        onRefresh = onRefresh
    ) {
        LazyColumn {
            items(
                items = articles,
                key = { it.id }
            ) { article ->
                ArticleItem(
                    article = article,
                    onClick = { onArticleClick(article) }
                )
            }
        }
    }
}

@Composable
private fun ArticleItem(
    article: Article,
    onClick: () -> Unit
) {
    Card(
        onClick = onClick,
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = 16.dp, vertical = 8.dp)
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(
                text = article.title,
                style = MaterialTheme.typography.titleMedium
            )
            Spacer(modifier = Modifier.height(4.dp))
            Text(
                text = "By ${article.author}",
                style = MaterialTheme.typography.bodySmall,
                color = MaterialTheme.colorScheme.onSurfaceVariant
            )
        }
    }
}
```

### Pattern 4: Dependency Injection with Hilt

```kotlin
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.HiltAndroidApp
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

// Application
@HiltAndroidApp
class MyApplication : Application()

// Module
@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Provides
    @Singleton
    fun provideRetrofit(): Retrofit = Retrofit.Builder()
        .baseUrl("https://api.example.com/")
        .addConverterFactory(MoshiConverterFactory.create())
        .build()

    @Provides
    @Singleton
    fun provideArticleApi(retrofit: Retrofit): ArticleApi =
        retrofit.create(ArticleApi::class.java)

    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext context: Context): AppDatabase =
        Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
            .build()

    @Provides
    fun provideArticleDao(database: AppDatabase): ArticleDao =
        database.articleDao()
}

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {

    @Binds
    @Singleton
    abstract fun bindArticleRepository(
        impl: ArticleRepositoryImpl
    ): ArticleRepository
}

// ViewModel with injection
@HiltViewModel
class ArticlesViewModel @Inject constructor(
    private val repository: ArticleRepository
) : ViewModel() {
    // Implementation
}
```

### Pattern 5: Kotlin DSL

```kotlin
// Type-safe builder pattern
class HtmlBuilder {
    private val elements = mutableListOf<String>()

    fun head(block: HeadBuilder.() -> Unit) {
        val builder = HeadBuilder()
        builder.block()
        elements.add("<head>${builder.build()}</head>")
    }

    fun body(block: BodyBuilder.() -> Unit) {
        val builder = BodyBuilder()
        builder.block()
        elements.add("<body>${builder.build()}</body>")
    }

    fun build(): String = "<html>${elements.joinToString("")}</html>"
}

class HeadBuilder {
    private val elements = mutableListOf<String>()

    fun title(text: String) {
        elements.add("<title>$text</title>")
    }

    fun build(): String = elements.joinToString("")
}

class BodyBuilder {
    private val elements = mutableListOf<String>()

    fun h1(text: String) {
        elements.add("<h1>$text</h1>")
    }

    fun p(text: String) {
        elements.add("<p>$text</p>")
    }

    fun div(block: BodyBuilder.() -> Unit) {
        val builder = BodyBuilder()
        builder.block()
        elements.add("<div>${builder.build()}</div>")
    }

    fun build(): String = elements.joinToString("")
}

fun html(block: HtmlBuilder.() -> Unit): String {
    val builder = HtmlBuilder()
    builder.block()
    return builder.build()
}

// Usage
val document = html {
    head {
        title("My Page")
    }
    body {
        h1("Welcome")
        div {
            p("Hello, World!")
        }
    }
}
```

### Pattern 6: Flow Operators

```kotlin
// Cold flow that emits values
fun fetchPaginatedData(): Flow<List<Item>> = flow {
    var page = 0
    while (true) {
        val items = api.getItems(page++)
        if (items.isEmpty()) break
        emit(items)
    }
}

// Flow operators
suspend fun processItems() {
    fetchPaginatedData()
        .map { items -> items.filter { it.isActive } }
        .flatMapConcat { items ->
            items.asFlow().map { enrichItem(it) }
        }
        .buffer(64) // Buffer emissions
        .flowOn(Dispatchers.IO) // Switch context
        .catch { e ->
            emit(emptyList())
            log.error("Failed to fetch", e)
        }
        .onStart { showLoading() }
        .onCompletion { hideLoading() }
        .collect { item ->
            processItem(item)
        }
}

// StateFlow and SharedFlow
class EventBus {
    private val _events = MutableSharedFlow<Event>(
        replay = 0,
        extraBufferCapacity = 64,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
    val events: SharedFlow<Event> = _events.asSharedFlow()

    suspend fun emit(event: Event) {
        _events.emit(event)
    }
}
```

## Best Practices

### 1. Use Immutable Data

```kotlin
// Prefer val over var
val items: List<Item> = listOf(...)

// Use data class copy for modifications
val updatedUser = user.copy(name = "New Name")
```

### 2. Leverage Kotlin Standard Library

```kotlin
// Collection operations
val names = users
    .filter { it.isActive }
    .sortedBy { it.name }
    .map { it.name }
    .distinct()

// Null-safe operations
val result = data?.takeIf { it.isValid }?.process()
```

### 3. Structured Concurrency

```kotlin
// Always use structured concurrency
coroutineScope {
    val result1 = async { fetchData1() }
    val result2 = async { fetchData2() }
    combine(result1.await(), result2.await())
} // All coroutines complete or fail together
```

## Common Pitfalls

- **GlobalScope Usage**: Avoid unstructured concurrency
- **Blocking in Coroutines**: Don't call blocking code without Dispatchers.IO
- **Memory Leaks**: Cancel coroutines in onDestroy/onCleared
- **Mutable State**: Prefer immutable data structures
- **Platform Types**: Handle Java nullability carefully
- **Flow Collection**: Collect flows in appropriate lifecycle scope

## Resources

- Kotlin Documentation
- Android Developers - Kotlin
- Kotlin Coroutines Guide
- Jetpack Compose Documentation

More Mobile skills

← All Mobile skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY