KMP Tutorial #6: Ktor Client — Networking in Kotlin Multiplatform

Every app needs to talk to the internet. On Android, you use Retrofit. On iOS, you use URLSession. Two different libraries, two different APIs, two different codebases. In KMP, you use Ktor Client — one networking library that works on every platform. Write your API calls once in commonMain, and they work on Android, iOS, Desktop, and Web. What is Ktor? Ktor is a networking framework by JetBrains (the same team behind Kotlin). The client side lets you make HTTP requests from shared code. ...

April 2, 2026 · 9 min

KMP Tutorial #5: KMP vs Flutter vs React Native — Which Should You Choose in 2026?

This is the most common question in cross-platform development: which framework should I use? The honest answer: there is no universal winner. Each framework has a different philosophy, different strengths, and fits different teams. This article will help you make an informed decision — not sell you on one framework. I will compare them across 10 dimensions with real data from 2026. The Three Philosophies Before we look at features, understand the core philosophy of each: ...

April 2, 2026 · 9 min

KMP Tutorial #4: Compose Multiplatform — Share Your UI Across Android, iOS, and Desktop

In the previous tutorials, we shared business logic between Android and iOS. The UI stayed separate — Compose on Android, SwiftUI on iOS. But what if you could share the UI too? That is what Compose Multiplatform (CMP) does. Write your Compose code once. Run it on Android, iOS, Desktop, and Web. Same @Composable functions. Same Modifier chains. Same MaterialTheme. If you followed our Jetpack Compose tutorial series, you already know how to build Compose UI. CMP uses the exact same API. No new framework to learn. ...

April 2, 2026 · 9 min

KMP Tutorial #3: Understanding KMP Project Structure — Source Sets, Dependencies, and expect/actual

In the previous tutorial, we created a KMP project and ran it on Android and iOS. Now let’s understand exactly how that project is organized — because once you understand the structure, everything else in KMP makes sense. This is the tutorial that separates developers who struggle with KMP from those who build confidently. Take your time. The Core Idea: Source Sets A KMP project is organized into source sets. Each source set is a collection of Kotlin files that compile to specific platforms. ...

April 1, 2026 · 12 min

KMP Tutorial #2: Setting Up Your First Kotlin Multiplatform Project

In the previous tutorial, we learned what KMP is and why it matters. Now let’s get our hands dirty — create a real project, understand every file in it, write shared code, and run it on both Android and iOS. This is a long tutorial. Take your time. By the end, you will have a working cross-platform app and understand exactly how every piece fits together. Part 1: Setting Up Your Environment What You Need Tool Required Purpose Android Studio Yes IDE for KMP development (latest stable) Xcode Yes (Mac only) Builds and runs iOS apps JDK 17+ Yes Kotlin compiler depends on it Mac computer For iOS Apple requires Xcode, which only runs on macOS CocoaPods Optional Some KMP libraries need it for iOS If you are on Windows or Linux, you can still write shared code and build the Android app. But you cannot build or run iOS. There is no way around this — Apple controls iOS builds. ...

April 1, 2026 · 14 min

KMP Tutorial #1: What is Kotlin Multiplatform and Why Should You Learn It?

You are a Kotlin developer. You build Android apps. But your company also needs an iOS app. And maybe a desktop app. And a web app. Do you write everything four times? Do you switch to Flutter and learn Dart? Do you hire a separate iOS team? Or do you use the Kotlin you already know — and share your code across all platforms? That is what Kotlin Multiplatform (KMP) does. ...

April 1, 2026 · 8 min

Jetpack Compose Tutorial #25: Publishing Your App to Google Play

This is it — the final tutorial. You built a complete task manager app with Jetpack Compose, Room, Hilt, Navigation, MVI, animations, and adaptive layouts. Now let’s put it in the hands of real users. Before You Publish — Checklist Make sure your app is ready: App works — test every feature on a real device No crashes — check Logcat for errors Dark mode works — test both themes Different screen sizes — test on phone and tablet Keyboard handling — forms work with keyboard visible Proguard/R8 — release build compiles and runs App icon — custom icon (not the default green Android) App name — set in strings.xml Step 1: Generate a Signing Key Every app on Google Play must be signed. This proves the app comes from you. ...

March 26, 2026 · 6 min

Jetpack Compose Tutorial #24: Navigation, Animations, and Polish

The task manager works. You can add tasks, complete them, delete them, search, and filter. But it doesn’t feel polished. Screens change instantly. Deleting a task is jarring. There’s no feedback when you complete something. This tutorial adds the polish that makes the difference between a homework project and a real app. What We Add Feature What It Does Navigation transitions Screens slide in/out smoothly Swipe to delete Swipe a task left to delete it Animated task completion Checkbox animates, strikethrough fades in Animated list changes Tasks slide in/out when added or removed Empty state animations Gentle fade-in when list is empty Dark mode Follows system theme Snackbar with undo “Task deleted” with undo option Navigation Transitions By default, screens appear instantly. Add slide transitions: ...

March 26, 2026 · 5 min

Jetpack Compose Tutorial #23: Building the UI Layer — Screens and ViewModels

In the previous tutorial, we built the data layer — Room entities, DAO, repository, and use cases. Now we connect it to the UI. This tutorial builds the three main screens of our task manager app: the task list, the add task form, and the settings screen. What We Build UI Layer: ├── TaskListScreen + TaskListViewModel (MVI) │ ├── Search bar │ ├── Filter chips (All, Active, Completed) │ ├── Task cards with priority badge + category │ └── Swipe to delete ├── AddTaskScreen + AddTaskViewModel │ ├── Title + description fields │ ├── Category chips │ ├── Priority chips │ └── Save button with validation └── SettingsScreen + SettingsViewModel ├── Dark mode toggle └── Delete completed tasks Task List Screen — State and Intents State // ui/screens/tasklist/TaskListState.kt // Everything the task list screen needs to display data class TaskListState( val tasks: List<Task> = emptyList(), val filter: TaskFilter = TaskFilter.ALL, val searchQuery: String = "", val isLoading: Boolean = true, val error: String? = null, val taskCount: Int = 0, val completedCount: Int = 0 ) Intents // ui/screens/tasklist/TaskListIntent.kt // Every action the user can take on the task list sealed interface TaskListIntent { data class Search(val query: String) : TaskListIntent data class ChangeFilter(val filter: TaskFilter) : TaskListIntent data class ToggleTask(val taskId: Long) : TaskListIntent data class DeleteTask(val taskId: Long) : TaskListIntent data object DeleteCompleted : TaskListIntent data object Retry : TaskListIntent } ViewModel // ui/screens/tasklist/TaskListViewModel.kt @HiltViewModel class TaskListViewModel @Inject constructor( private val getTasks: GetTasksUseCase, private val toggleTask: ToggleTaskUseCase, private val deleteTask: DeleteTaskUseCase, private val repository: TaskRepository ) : ViewModel() { private val _state = MutableStateFlow(TaskListState()) val state: StateFlow<TaskListState> = _state.asStateFlow() private var searchJob: Job? = null init { observeTasks() observeCounts() } fun onIntent(intent: TaskListIntent) { when (intent) { is TaskListIntent.Search -> { _state.update { it.copy(searchQuery = intent.query) } observeTasks() } is TaskListIntent.ChangeFilter -> { _state.update { it.copy(filter = intent.filter) } observeTasks() } is TaskListIntent.ToggleTask -> { viewModelScope.launch { toggleTask(intent.taskId) } } is TaskListIntent.DeleteTask -> { viewModelScope.launch { deleteTask(intent.taskId) } } is TaskListIntent.DeleteCompleted -> { viewModelScope.launch { repository.deleteCompletedTasks() } } is TaskListIntent.Retry -> { _state.update { it.copy(error = null) } observeTasks() } } } private fun observeTasks() { searchJob?.cancel() searchJob = viewModelScope.launch { _state.update { it.copy(isLoading = true) } try { getTasks( filter = _state.value.filter, searchQuery = _state.value.searchQuery ).collect { tasks -> _state.update { it.copy(tasks = tasks, isLoading = false, error = null) } } } catch (e: Exception) { _state.update { it.copy(error = e.message, isLoading = false) } } } } private fun observeCounts() { viewModelScope.launch { repository.getTaskCount().collect { count -> _state.update { it.copy(taskCount = count) } } } viewModelScope.launch { repository.getCompletedCount().collect { count -> _state.update { it.copy(completedCount = count) } } } } } The ViewModel pattern: ...

March 26, 2026 · 9 min

Jetpack Compose Tutorial #22: Building the Data Layer — Room + Repository Pattern

In the previous tutorial, we planned our task manager app. Now we build the foundation — the data layer. The data layer is everything below the UI: the database, the repository, and the use cases. Get this right, and the UI layer writes itself. What We Build in This Tutorial data/ ├── local/ │ ├── TaskEntity.kt ← Database table definition │ ├── TaskDao.kt ← Database operations │ └── AppDatabase.kt ← Room database ├── mapper/ │ └── TaskMapper.kt ← Convert Entity ↔ Domain Model ├── repository/ │ └── TaskRepositoryImpl.kt ← Repository implementation │ domain/ ├── model/ │ ├── Task.kt ← Clean domain model │ ├── Category.kt ← Enum │ └── Priority.kt ← Enum ├── repository/ │ └── TaskRepository.kt ← Interface (contract) └── usecase/ ├── GetTasksUseCase.kt ├── AddTaskUseCase.kt ├── ToggleTaskUseCase.kt └── DeleteTaskUseCase.kt Step 1: Domain Models The domain layer is pure Kotlin — no Android dependencies, no Room annotations. This is the “truth” of your app. ...

March 25, 2026 · 11 min