Skip to content

serializer/compose_multiplatform_template

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Compose Multiplatform Template

A production-ready Compose Multiplatform template with modern Android/iOS architecture, featuring Navigation 3, Room Database, DataStore, Koin DI, and Ktor networking.

Tech Stack

Library Version Purpose
Compose Multiplatform 1.10.0-beta02 UI Framework
Kotlin 2.2.21 Language
Navigation 3 1.0.0-alpha05 Type-safe Navigation
Room 2.8.4 Local Database
DataStore 1.2.0 Preferences Storage
Koin 4.0.4 Dependency Injection
Ktor 3.1.3 Networking
Kotlinx Serialization 1.9.0 JSON Parsing
Kotlinx Coroutines 1.10.2 Async Operations

Project Structure

composeApp/src/
β”œβ”€β”€ commonMain/kotlin/dev/serializer/template/cmp/
β”‚   β”œβ”€β”€ data/
β”‚   β”‚   β”œβ”€β”€ local/
β”‚   β”‚   β”‚   β”œβ”€β”€ dao/UserDao.kt
β”‚   β”‚   β”‚   β”œβ”€β”€ entity/UserEntity.kt
β”‚   β”‚   β”‚   β”œβ”€β”€ AppDatabase.kt
β”‚   β”‚   β”‚   └── DatabaseBuilder.kt (expect)
β”‚   β”‚   β”œβ”€β”€ preferences/
β”‚   β”‚   β”‚   └── PreferencesDataStore.kt
β”‚   β”‚   β”œβ”€β”€ remote/
β”‚   β”‚   β”‚   β”œβ”€β”€ model/PostDto.kt
β”‚   β”‚   β”‚   β”œβ”€β”€ ApiService.kt
β”‚   β”‚   β”‚   β”œβ”€β”€ HttpClientFactory.kt
β”‚   β”‚   β”‚   └── NetworkClient.kt
β”‚   β”‚   └── repository/
β”‚   β”‚       └── UserRepository.kt
β”‚   β”œβ”€β”€ di/
β”‚   β”‚   └── AppModule.kt
β”‚   β”œβ”€β”€ domain/
β”‚   β”‚   └── model/User.kt
β”‚   β”œβ”€β”€ navigation/
β”‚   β”‚   β”œβ”€β”€ Routes.kt
β”‚   β”‚   └── AppNavigation.kt
β”‚   β”œβ”€β”€ presentation/
β”‚   β”‚   β”œβ”€β”€ screens/
β”‚   β”‚   β”‚   β”œβ”€β”€ HomeScreen.kt
β”‚   β”‚   β”‚   β”œβ”€β”€ DetailScreen.kt
β”‚   β”‚   β”‚   └── SettingsScreen.kt
β”‚   β”‚   └── viewmodel/
β”‚   β”‚       β”œβ”€β”€ HomeViewModel.kt
β”‚   β”‚       └── SettingsViewModel.kt
β”‚   β”œβ”€β”€ util/
β”‚   β”‚   β”œβ”€β”€ Result.kt
β”‚   β”‚   └── TimeUtils.kt
β”‚   └── App.kt
β”œβ”€β”€ androidMain/kotlin/...
β”‚   β”œβ”€β”€ CMPApplication.kt
β”‚   β”œβ”€β”€ MainActivity.kt
β”‚   β”œβ”€β”€ DatabaseBuilder.android.kt (actual)
β”‚   β”œβ”€β”€ PreferencesDataStore.android.kt (actual)
β”‚   β”œβ”€β”€ HttpClientFactory.android.kt (actual)
β”‚   └── TimeUtils.android.kt (actual)
└── iosMain/kotlin/...
    β”œβ”€β”€ MainViewController.kt
    β”œβ”€β”€ DatabaseBuilder.ios.kt (actual)
    β”œβ”€β”€ PreferencesDataStore.ios.kt (actual)
    β”œβ”€β”€ HttpClientFactory.ios.kt (actual)
    └── TimeUtils.ios.kt (actual)

Features

Navigation 3

Type-safe navigation with serializable routes:

// Define routes
@Serializable
sealed interface Route {
    @Serializable
    data object Home : Route

    @Serializable
    data class Detail(val id: String) : Route
}

// Navigate
backStack.add(Route.Detail(id = "123"))

Room Database (KMP)

Full Room support for both Android and iOS:

@Entity(tableName = "users")
data class UserEntity(
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0,
    val name: String,
    val email: String
)

@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun getAllUsers(): Flow<List<UserEntity>>

    @Insert
    suspend fun insertUser(user: UserEntity): Long
}

NetworkClient with Result Type

Type-safe networking with automatic error handling:

// Simple API calls
class ApiService(private val networkClient: NetworkClient) {
    suspend fun getPosts(): Result<List<PostDto>> =
        networkClient.get("/posts")

    suspend fun createPost(post: PostDto): Result<PostDto> =
        networkClient.post("/posts", post)
}

// Handle results elegantly
apiService.getPosts()
    .onSuccess { posts -> /* handle success */ }
    .onError { exception -> /* handle error */ }

// Or transform data
apiService.getPosts()
    .map { posts -> posts.filter { it.userId == 1 } }
    .getOrNull()

Result Type

Comprehensive error handling:

sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: AppException) : Result<Nothing>()
    data object Loading : Result<Nothing>()
}

// Exception types
sealed class AppException {
    data class NetworkException(...)   // Connection issues
    data class ServerException(...)    // 5xx errors
    data class ClientException(...)    // 4xx errors
    data class SerializationException(...) // JSON parsing
    data class TimeoutException(...)   // Request timeout
    data class UnknownException(...)   // Other errors
}

DataStore Preferences

Cross-platform preferences storage:

class PreferencesRepository(private val dataStore: DataStore<Preferences>) {
    val darkModeFlow: Flow<Boolean> = dataStore.data.map {
        it[PreferencesKeys.DARK_MODE] ?: false
    }

    suspend fun setDarkMode(enabled: Boolean) {
        dataStore.edit { it[PreferencesKeys.DARK_MODE] = enabled }
    }
}

Koin Dependency Injection

Simple and powerful DI:

val appModule = module {
    // Database
    single { getDatabaseBuilder().build() }
    single { get<AppDatabase>().userDao() }

    // Network
    single { createHttpClient() }
    single { NetworkClient(get(), ApiConfig.BASE_URL) }
    singleOf(::ApiService)

    // ViewModels
    viewModelOf(::HomeViewModel)
}

Getting Started

Prerequisites

  • Android Studio Ladybug or later
  • Xcode 15+ (for iOS)
  • JDK 17+

Build & Run

Android:

./gradlew :composeApp:assembleDebug

iOS:

./gradlew :composeApp:compileKotlinIosSimulatorArm64
# Then open iosApp/iosApp.xcodeproj in Xcode

Configuration

Update the base URL in AppModule.kt:

object ApiConfig {
    const val BASE_URL = "https://your-api.com"
}

Sample Data Notice

Important: The sample app includes dummy data for demonstration purposes only:

  • Users: Stored locally in Room database - you can create and delete users within the app
  • Posts: Fetched from JSONPlaceholder - a free fake REST API for testing and prototyping

When building your app:

  1. Replace the BASE_URL in AppModule.kt with your actual API endpoint
  2. Update DTOs in data/remote/model/ to match your API response structure
  3. Modify ApiService.kt with your actual API endpoints
  4. Update Room entities and DAOs for your data models

Architecture

The template follows Clean Architecture principles:

Presentation Layer (UI)
    ↓
Domain Layer (Business Logic)
    ↓
Data Layer (Repository Pattern)
    ↓
Data Sources (Room, Ktor, DataStore)

Data Flow

  1. UI observes StateFlow from ViewModel
  2. ViewModel calls Repository/ApiService methods
  3. Repository coordinates between local (Room) and remote (Ktor) data sources
  4. Result type ensures type-safe error handling throughout

Customization Guide

Adding a New Screen

  1. Create route in Routes.kt:
@Serializable
data class NewScreen(val param: String) : Route
  1. Create screen composable in presentation/screens/

  2. Add to AppNavigation.kt:

is Route.NewScreen -> NavEntry(key) {
    NewScreenContent(param = key.param)
}

Adding a New API Endpoint

  1. Add DTO in data/remote/model/:
@Serializable
data class ItemDto(val id: Int, val name: String)
  1. Add method to ApiService.kt:
suspend fun getItems(): Result<List<ItemDto>> =
    networkClient.get("/items")

Adding a New Database Entity

  1. Create entity in data/local/entity/:
@Entity(tableName = "items")
data class ItemEntity(
    @PrimaryKey val id: Long,
    val name: String
)
  1. Create DAO in data/local/dao/:
@Dao
interface ItemDao {
    @Query("SELECT * FROM items")
    fun getAll(): Flow<List<ItemEntity>>
}
  1. Add to AppDatabase.kt:
@Database(entities = [UserEntity::class, ItemEntity::class], ...)
abstract class AppDatabase : RoomDatabase() {
    abstract fun itemDao(): ItemDao
}
  1. Register DAO in AppModule.kt:
single { get<AppDatabase>().itemDao() }

API Reference

NetworkClient Methods

Method Description
get<T>(endpoint) GET request returning Result<T>
post<T, B>(endpoint, body) POST request with body
put<T, B>(endpoint, body) PUT request with body
patch<T, B>(endpoint, body) PATCH request with body
delete<T>(endpoint) DELETE request
getOrNull<T>(endpoint) GET returning T? (null on error)
getOrThrow<T>(endpoint) GET returning T (throws on error)

Result Methods

Method Description
onSuccess { } Execute block on success
onError { } Execute block on error
map { } Transform success value
flatMap { } Transform to another Result
getOrNull() Get value or null
getOrThrow() Get value or throw exception

Known Limitations

  • Navigation 3 is in alpha - API may change in future releases
  • Compose Multiplatform 1.10.0 is in beta - some features may be unstable
  • Room's @ConstructedBy annotation shows beta warnings (can be suppressed)
  • kotlinx-datetime Clock.System has issues on iOS - using platform-specific currentTimeMillis() as workaround

License

This template is available under the MIT License. Feel free to use it for your projects.


Learn more about Kotlin Multiplatform

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published