Blog Infos
Author
Published
Topics
,
Published
Topics
,

Photo by NASA on Unsplash

 

Hey everyone, I want to share more of Kotlin Multiplatform Mobile Content since it is much more stable now. I am building this side project as a learning knowledge-sharing example and will focus on something other than the UI/UX.

The main goal is to give you an overall understanding of the project. I will be sharing a step-by-step hands-on project that will help you understand the basics of the KMM.

Environment Setup
1. Install Kotlin Multiplatform Mobile Plugin [Link]

We first need to add this Kotlin Multiplatform Mobile Plugin, which will do the heavy lifting and set up the project for us. I will be using Android Studio: Hedgehogjdk 17 and Xcode. After adding the plugin restart Android Studio and click on create new projectIt would be great if you could use kdoctor which ensures that all required components are properly installed and ready for use. If something is missed or not configured, KDoctor highlights the problem and suggests how to fix the problem.

2. Project Creation
  • Now you will find a new template in this section select Kotlin Multiplatfrom App and click on next.

  • Next, give the name of your app in our case Animax, you can change the package name and minimum SDK version according to your need and click on next.
  • Finally, I will advise you to keep everything be same just change the iOS framework distribution to CocoaPods Dependency Manger (Must do this) and click on finish.
  • Wait for a good amount of time 🙂
3. Starting Up and Basic Overview

After you are done with the Gradle setup you will have a project structure which you must be familiar with. Think of it as a multimodule project, you will have modules for androidAppiosApp and shared.

As the name suggests the shared will have the logic and the shareable portion of the application, the network, the local database and their respective implementation. The shared module can be further divided into androidMainiosMain and commonMainandroidMain and iosMain contain the Android and iOS-specific implementation we will discuss more of it when we dive into the coding.

Background Context

Before we start coding we need to understand how the data flows in a KMM App. In this example, we are trying to build an app which makes an API call and renders the details on the screen. So we can divide the app into two phases, the Data Transaction Layer and the UI Rendering Layer.

  • The UI Rendering Layer is simply a dumb layer on the respective platform which just renders the data and is not aware of where the data is coming from, we will be using Jetpack Compose and Swift on this layer.
  • The Data Transaction Layer will mostly focus on how to access the required data, from API, local database or common business logic. It also focuses on platform-specific implementation like determining which implementation of the function to take based on the platform the user is using.

Kotlin Official Site

 

We will use the special keywords expectand actualin a scenario where there will be different implementations based on the platform, in this instance the API Client. With this technique, the anticipated declaration is defined commonMain, and platform source sets are required to supply the actual declaration that matches the expected declaration. The compiler makes sure that every declaration in the common module marked with the expectkeyword has the matching declarations in all platform modules tagged with the actualkeyword.

The shared module is imported as a part of the dependency in both build.gradle.kts and Podfile

Building KMM App 🚀

Will be building a simple app which will make an API call and rending the image. Here is the project, you can check out the code here.

The Open API that we will be using for getting the anime response.

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

Jobs

  • Dependencies Setup: We will start by adding dependencies to the commonMain in build.gradle [code preview]. We will add platform-specific, dependencies to androidMain and iosMain in this case, we have added the dependency for Ktor client.
//Common Dependencies with go under commonMain
val commonMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-core:$ktorVersion")
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
                implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
                implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
            }
        }

//Adding Android Specific client
val androidMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
            }
        }

//Adding iOS Specific client
val iosMain by creating {
            dependsOn(commonMain)
            iosX64Main.dependsOn(this)
            iosArm64Main.dependsOn(this)
            iosSimulatorArm64Main.dependsOn(this)
            dependencies {
                implementation("io.ktor:ktor-client-darwin:$ktorVersion")
            }
        }
  • API Client Setup: Making API Client will be simple. We have already discussed ⁣expect keyword which provides us with the required platform-specific implementation [code preview]. In this case httpClient() is expected to return a platform-specific HttpClient instance, which is further user to add configuration, regarding JSON formatting. We will be using networkClientto make API calls in the later state.
/*
  File Name: NetworkClient.kt
  Location: shared/src/commonMain/kotlin/com.debanshu.animax
*/

expect fun httpClient(config: HttpClientConfig<*>.() -> Unit = {}):HttpClient

val networkClient:HttpClient = httpClient {
    install(ContentNegotiation){
        json(Json {
            prettyPrint = true
            isLenient = true
            ignoreUnknownKeys = true
        })
    }
}

We will now make the actual function to implement the httpClient() under these specified locations mentioned below, this is the actual platform-specific implementation of how the client object is created.

/*
  File Name: OkhttpNetworkClient.kt
  Loaction: shared/src/androidMain/kotlin/com.debanshu.animax
*/
actual fun httpClient(config: HttpClientConfig<*>.() -> Unit)= HttpClient(OkHttp){
    config(this)
    engine {
        config {
            retryOnConnectionFailure(true)
            connectTimeout(0,TimeUnit.SECONDS)
        }
    }
}
/*
  File Name: DarwinNetworkClient.kt
  Loaction: shared/src/iosMain/kotlin/com.debanshu.animax
*/
actual fun httpClient(config: HttpClientConfig<*>.() -> Unit)= HttpClient(Darwin) {
    config(this)
    engine {
        configureRequest {
            setAllowsCellularAccess(true)
        }
    }
}
  • Building API call Setup: We will be following a bit of Clean architecture here, by dividing each use case. In our cases, we have only one use case i.e., to get popular anime. To make it extendable we will make an abstract BaseUsecase and then we can simply implement it as GetTopAnimeUsecase. We will also put model cases here TopAnimeResponse is a model class inside shared/src/commonMain/kotlin/com.debanshu.animax/model[code preview]
/*
  File Name: BaseUseCase.kt
  Loaction: shared/src/iosMain/kotlin/com.debanshu.animax/usecase
*/
abstract class BaseUseCase<REQUEST,RESPONSE> {
    @Throws(Exception::class)
    abstract suspend fun execute(request: REQUEST):RESPONSE
}
/*
  File Name: GetTopAnimeUseCase.kt
  Loaction: shared/src/iosMain/kotlin/com.debanshu.animax/usecase
*/
class GetTopAnimeUseCase : BaseUseCase<Unit, TopAnimeResponse>() {
    override suspend fun execute(request: Unit): TopAnimeResponse {
        val response= networkClient.get("https://api.jikan.moe/v4/top/anime")
        return response.body()
    }
}
  • Building UI: We will not focus much on this as it is not the focus of the current tutorial, we will just understand how we can access the shared code in our native UI. For both Android and iOS GetTopAnimeUsecase is the direct way to access and trigger the function. This is obviously not the ideal way to access the data in the app, but it is for demonstration purposes.
/*
For Android
Location: androidApp/src/main/java/com.debanshu.animax.android/MainActivity.kt
*/
.....
var animeList by remember {
        mutableStateOf(emptyList<Anime>())
    }
    LaunchedEffect(true) {
        scope.runCatching {
            GetTopAnimeUseCase().execute(Unit)
        }.onSuccess { remoteAnime ->
            animeList = remoteAnime.data
        }.onFailure {
            Log.d("Failed", "Network error")
        }
    }
....

/* 
For iOS
Loaction: iosApp/iosApp/ContentView.swift
*/
.....
 GetTopAnimeUseCase().execute(request: KotlinUnit()){ topAnimeRespose, error in
                guard let animeList = topAnimeRespose?.data else {return}
                DispatchQueue.main.async {
                    self.topAnimeList=animeList
                }
            }
.....

Finally, we are done with a basic example of Kotlin Multiplatform Mobile✨.

For any doubts and suggestions, you can reach out on my Instagram, or LinkedIn. Follow me for Kotlin content and more. Happy Coding!

I will well appreciate one of these 👏

Recent Posts

This article was previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
blog
Drag and Drop reordering in Recyclerview can be achieved with ItemTouchHelper (checkout implementation reference).…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu