kotlin-development
Master Kotlin for Android and JVM development with coroutines, modern language features, and idiomatic patterns for building robust applications.
Works with
---
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 DocumentationMore Mobile skills
animation-vocabulary
emilkowalski/skills
Reverse-lookup glossary that turns a vague description of a web animation or motion effect into its exact term ("the bouncy thing when a popover opens" → Pop in; "the iOS rubber-band scroll" → Rubber-banding). Use when the user asks "what's it called when…", or describes a motion effect without knowing its name and wants the right word to prompt an AI or designer with. For naming an effect, not designing or building one.
xcode-project-setup
firebase/agent-skills
Safely modifies Xcode projects (.pbxproj) to add Swift Packages and link files. Use this skill whenever an iOS project needs dependencies installed (e.g. Firebase, Alamofire).
cross-border-ecommerce
nexscope-ai/ecommerce-skills
Cross-border e-commerce expansion advisor. Scores target markets on 8 weighted dimensions (market size, ecommerce penetration, competition, regulatory complexity, logistics infrastructure, payment ecosystem, cultural distance, IP protection), compares 5 fulfillment models with cost and transit data, provides country-by-country tax/duty compliance guides (EU VAT/IOSS, UK VAT, US sales tax, CA GST, AU GST, JP consumption tax), maps local payment preferences by market, and builds a phased expansion roadmap. No API key required.

