In a KMM project, the goal is to maximize code reuse. The business logic remains consistent across platforms, so it’s logical to have the domain layer, written in pure Kotlin, in the shared module. This layer can be reused across web, mobile, and terminal platforms. Here, we define the contracts or interfaces for repositories and interactors. In our example, we have a single interactor called GetMoviesInteractor, responsible for retrieving movies. It depends on the MovieRepository interface in the domain package.

class GetMoviesInteractorImpl(private val repo: MovieRepository) {
    override suspend fun execute(): List {
        return repo.getMovies()

In the domain layer, the classes and functions are independent of any specific platform framework, such as iOS or Android. They rely only on their own definitions. The GetMoviesInteractor constructor expects an object that implements the MovieRepository interface and returns objects within the same layer.

You may question the need for a use case that simply calls a repository function. While it may not appear significant initially (especially in small apps), using interactors can simplify the construction and maintenance of business logic.

For instance, let’s say our Movie model has a directorDetail attribute of type MovieDirector. The movie repository might not have the director information, requiring us to consult another repository for the details.

class GetMoviesInteractorImpl(
    private val moviesRepo: MovieRepository,
    private val directorsRepo: MovieDirectorRepository,
    private val productionCompanyRepository: ProductionCompanyRepository
) {
    override suspend fun execute(): List {
        val movies = moviesRepo.getAll()
        val moviesDetail = { movie ->
                movie = movie,
                director = directorsRepo.getDirectorDetailById(movie.directorId),
                productionCompany = productionCompanyRepository.getCompanyDetailById(movie.productionCompanyId)
        return moviesDetail

In the shared module, we organize our shared application core as follows:

  • androidMain: This includes a SqlDelight implementation for Android, serving as the database driver factory.
  • iosMain: Similar to androidMain, this implements the SqlDelight database instance for iOS.
  • commonMain: This is where the multiplatform magic happens. It contains divisions for the domain and data layers. The domain package includes the interactor, Movie model, and repository interface. Additionally, we have a DispatcherProvider.kt defining the Dispatchers for Kotlin Coroutines, to be used in data layer implementations of repositories or data sources.

In the androidMain and iosMain packages, you’ll find platform-specific Kotlin code, such as the SQLight database manager. However, the business/domain layer code is located in commonMain for maximum reusability and independence.

It’s important to note that the domain layer, implemented in the domain package within the shared/commonMain module, will be reused by both platforms and the data layer. Hence, its implementation is not included in androidMain and iosMain.

One more thing: make sure to add the Kotlin Coroutines dependency to our shared module to leverage its power.

// shared/build.gradle.kts
sourceSets {
    val coroutineVersion = "1.6.0-native-mt"
    val commonMain by getting {
        dependencies {
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion") {
                version { strictly(coroutineVersion) }

Stay tuned for the next post, where we’ll explore the structure of the data layer!

You can follow the progress of the project on GitHub:

What’s Next

This has been the third post in which we have talked about the domain layer. In the following, we will talk about the data layer. This guide is divided into the following posts:

  1. Introduction
  2. Designing the solution
  3. Creating the domain layer (this post)
  4. Creating the layer data
  5. Implementing the presentation layer