Loading...
Home
  • Tech Blogs
  • Videos
  • Conferences
    • Droidcon News
    • Upcoming Conferences
    • Become a Partner
    • Past Events
    • Keep Me Informed
    • Diversity Scholarships
  • Community
    • droidcon Team
    • How to Hold a Droidcon
  • Android Careers
Sign In

Global CSS

 

Kotlin Multiplatform + Rx + MVVM

 

 
Hossein Abbasi
Senior Android Developer at SafeBoda https://www.linkedin.com/in/drjacky/
Published: August 11, 2020
Tweet
Share
 

Image by wallpaperaccess.com

What is better for a developer than writing code once and run it on various platforms, natively? (but in real! and not like Java which runs anywhere(WORA) that a JVM only exists!)

Let me quote from Kotlin website to show you the power of Kotlin and what we’re gonna talk about in this article:

 

Working on all platforms is an explicit goal for Kotlin, but we see it as a premise to a much more important goal: sharing code between platforms. With support for JVM, Android, JavaScript, iOS, Linux, Windows, Mac and even embedded systems like STM32, Kotlin can handle any and all components of a modern application. And this brings the invaluable benefit of reuse for code and expertise, saving the effort for tasks more challenging than implementing everything twice or multiple times.

So, let’s start! 💪🏼


IDEs 🖥

  • Android Studio: v4.0
  • Xcode: v11.5

Note: It is possible to use IntelliJ IDEA IDE as well, but in that case, we would configure some other stuff like enabling Android Support in Plugin, gradle wrapper, etc. Yes, IntelliJ IDEA has its own benefits as well like predefined gradle files, but I’d prefer to work with the official IDEs.

 

Libraries 📚

  • Kotlin: v1.3.72
  • Reaktive(Kotlin multiplatform implementation of RXs): v1.1.13
  • Ktor(asynchronous Web framework for Kotlin): v1.3.2
  • Kodein(A multiplatform Dependency Injection library): 7.0.0
  •  

Goal 🎯

Step by step creating a working sample app, then at the end, a simple IMDb App that shows a list of movies fetches from OMDb API.

Source code:

 

Drjacky/OMDbMultiplatform

Step-by-step guide on Kotlin Multiplatform. Contribute to Drjacky/OMDbMultiplatform development by creating an account…

github.com

 


Let’s start

1- Create an Empty Activity Android project.

2- Rename app module to android module; And do the same in settings.gradle.kts .

3- Create a shared folder(or any name you want) in the root of the project. This is the place we’re gonna put the shared business logic between platforms.

4- Add shared module to the settings.gradle.kt.

5- shared folder is gonna contain iosMain (actual), androidMain (actual) and commonMain (expect) source sets.

6- Note: Read more about actual and expect keywords here.

7- Create build.gradle.kts in shared module and put this:

 

 

We recommend that you disable automatic build import, but enable automatic reloading of script dependencies. That way you get early feedback while editing Gradle scripts and control over when the whole build setup gets synchronized with your IDE.

And yes, a few disadvantages on Android Studio(👉 not anymore on v4.0.0 and above 👈) like we’re gonna miss “Project Structure”:

 

Image for post

 

9- Note: To targeting android, we must use android() and notjvm("android") .

10- The reason is when we use android(), we have to create AndroidManifest.xml (containing package name) in androidMain folder + THIS lines and scope in shared's build.gradle.kts file:

 

Image for post

 

Otherwise, we can not build the project:

 

Image for post

 

11- You can choose one of the following solutions to detect if it’s a simulator or actual Apple device:

 

Image for post

They’re setting this flag since IntelliJ 15 but I don’t suggest this solution, cause it might be changed later.

 

Image for post

 

12- If you want to support older devices, consider iosArm32 too; Which you can find supported devices with their OS here:

 

Image for post

Image by isupportmatrix

 

13- For now, I’m gonna put versions into gradle.properties in the root of the project. Later, I’ll separate modules with their own build.gradle files.

14- We’re gonna create a temporary function to just have a sample app for now and run it on iOS and Android. So, create shared/src/commonMain/kotlin/common.kt.

15- Create shared/src/androidMain/kotlin/common.kt and shared/src/iosMain/kotlin/common.ktaand create platfrom method with actual keyword and your desired method body.

16- Note: in android module’s build.gradle.kts you can use plugins { id("kotlin-multiplatfrom") or plugins { id("kotlin-android"). In case of the first choice, you’d need to put dependencies scope into kotlin { sourceSets {scope and add android() as well.

But apart from this, in the case of choosing the first option, you’d have more than one “multiplatform” plugin which might be a cause for possible issues.

17- Use HelloWorldMessage() in the Android module and run the Android app:

 

Image for post

 

15- Create an iOS project: Create a new Xcode project -> iOS target -> Single View App and then fill the form and choose the root of the previous project. Remember to choose Storyboard for the user interface, instead of SwiftUI.

16- Note: Check gradle-wrapper.properties and sure Gradle version is more than 4.7 (which I suggest to set it on class six (6.X.X)). Why? Run ./gradlew --version on different gradle versions.

In this case, using val kotlinVersion: String by extra doesn’t work in settings.gradle.kts. So, we need to set the Kotlin version using requested.version which use the version of the plugin if one was specified, otherwise null. So:

 

Image for post

 

17- Run packForXcode task to setup iOS Framework in the Xcode project model easily, whether with right Gradle panel in AS, Android Studio terminal, or Mac terminal.

You probably(👉 I didn’t get this error after I update Xcode, Android Studio and Kotlin version 👈) get this warning:

 

Image for post

 

Which addressed here, here, etc.

18- Now, we need to add the shared framework to the Xcode project to be able to use common resources on iOS as well.

Go to Xcode, double click on the root of the project name(the blue icon: ios) to open target settings.

Under General tab, Under Frameworks, Libraries, and Embedded Content, press + -> Add Other… -> Add Files… -> address shared/build/xcode-frameworks/shared.framework folder.

19- Now we need to tell to the Xcode where to look for the framework.

Under the Build Settings tab, search for “Framework Search Paths”. Now add this relative path to both Debug and Release: “$(SRCROOT)/../shared/build/xcode-frameworks”.

20- Now we need to tell to the Xcode build our shared framework before each run.

Under the Build Phases -> + -> New Run Script Phase -> paste this snippet code in Shell content:

 

Image for post

 

And drag this task on top of the other tasks.

21- Use HelloWorldMessage() in the ViewController.swift.

Now run the iOS app:

 

Image for post

 


Let’s start to build the OMDb App

22- Note: I’ve checked lots of articles and open-source projects (as I listed some of them as a reference).

For now, I’m gonna follow with some of Fandy Gotama’s snippet codes (which I do not agree with all of them) but, the idea is to have a working application; Especially, as I’m not an iOS developer(yet😄). Later on, I’m gonna improve and change the project and update the article.

23- Add the necessary APIs to the project. For example for Reactive:

Root’s build.gradle file:

 

allprojects {
    repositories {
        mavenCentral()
        ...
        maven("https://dl.bintray.com/badoo/maven")google()
        jcenter()
    }
}

 

And in the shared’s build.gradle.kt:

 

val reactiveVersion: String by extra...sourceSets {
        val commonMain by getting {
            dependencies {
                ...
                implementation("com.badoo.reaktive:reaktive:$reactiveVersion")
            }
        }
        ...
    }}

 

24- About kotlinx.serialization, take it into account after Kotlin v1.4-M2:

 

Specifying dependencies only once

From now on, instead of specifying dependencies on different variants of the same library in shared and platform-specific source sets where it is used, you should specify a dependency only once in the shared source set.

Don’t use kotlinx library artifact names with suffixes specifying the platform, such as -common, -native, or similar, as they are NOT supported anymore. Instead, use the library base artifact name, which in the example above is kotlinx-coroutines-core. However, the change doesn’t currently affect the stdlib and kotlin.test libraries (stdlib-common and test-common); they will be addressed later.

If you need a dependency only for a specific platform, you can still use platform-specific variants of standard and kotlinx libraries with such suffixes as -jvm or-js, for example kotlinx-coroutines-core-jvm.

 

25- Regards enabling experimental features, please take a look here.

26- For Ktor version, take the latest version from the releases section, and not the download section of the Github: ❌👇

 

Image for post

 

27- Remember if you tried the newer unstable Kotlin version, then regretted and set it back to a stable version(1.3.X), be sure that you uninstall Kotlin Plugin(with unstable version) and restart the IDE(to get the stable version automatically). Otherwise, you get this error:

 

Image for post

 

28- Note: If you use kotlin() instead of id(), you must change dash(-) with a dot(.) and remove kotlin keyword if it exists. For example:

 

Image for post

 

29- Objective-C supports “lightweight generics” defined on classes, with a relatively limited feature set. Swift can import generics defined on classes to help provide additional type information to the compiler.

Generic feature support for Objc and Swift differ from Kotlin, so the translation will inevitably lose some information, but the features supported retain meaningful information.

Generics are currently not enabled by default. To have the framework header written with generics, add an experimental flag to the compiler config:

 

iOSTarget("ios") {
    binaries {
        framework {
            baseName = "shared"
            freeCompilerArgs += "-Xobjc-generics"
        }
    }
}

 

For Kotlin/Native interoperability with Swift/Objective-C, check out here.

30- To have the logging feature in Ktor, check out this.

31- [Out of Date-Check #44]For determining build type in the shared module, there are a few tricks and libraries but, I preferred to pass the value(API_KEY) from android module to the shared module.

A minor advantage of this solution is, we avoid rebuilding the shared module when the API URL and token get changed.[Out of Date-Check #44]

32- Note: For the architecture, it’s not 100% MVVM that all we know; Cause we have to take care of our object lifecycle by ourselves. There is moko-mvvm but, I didn’t want to rely on a third-party component for the architecture. Maybe later when I try to improve the architecture(as I mentioned on ‘under development’ section), I realize that that component is the best solution so far.

 

33- If you use opt-in requirements for features in the experimental state, carefully handle the API graduation to avoid breaking the client code.

 

To use experimental opt-in annotation, we need to add -Xopt-in=kotlin.RequiresOptIn to the compiler argument. I’ve passed as the second parameter to sections where we defined jvmTarget = “1.8” but, didn’t work for me. So, I’ve used it like below in the shared’s build.gradle, inside sourceSet scope:

 

Image for post

 

34- About Ktor client, we used the built-in install method to implement the JSON feature and use kotlin serialization.

35- Note: we use suspend function from coroutines because it's required by Ktor.

36- I’m not gonna explain implemented architecture. If you have any doubts or questions regards Clean Architecture, you could take a look into this article of mine.

37- As I earlier mentioned, Ktor uses coroutines to do the asynchronous task. So, we need to transform suspend fun into the observable stream. One solution is using coroutines-interop but, due to its temporary limitations and current bugs, do not use this for now:

 

implementation 'com.badoo.reaktive:coroutines-interop:<latest-version>'

 

For more information, please read this.

38- About ViewModel, just keep it in your mind that Inputs represents any interaction/input from the view and Outputs represents changes from the ViewModel that the view has to display. To understand it better, please take a look into this.

39- To work with Inputs and Outputs in the ViewModel on all platforms, we created ViewModelBinding.

40- For image loading in iOS, I’m using Nuke library.

To add an external library to your project in XCode, first, connect your IDE to your GitHub account from Preferences -> Account, then to add a library from Github, follow File -> Swift Package -> Add Package Dependency.

41- About nil keyword in Swift:

 

Swift’s nil isn’t the same as nil in Objective-C. In Objective-C, nil is a pointer to a nonexistent object. In Swift, nil isn’t a pointer—it’s the absence of a value of a certain type. Optionals of any type can be set to nil, not just object types.

 

In another word, it’s null in Java/Kotlin/etc.

42- In iOS project, we don’t pass MoviesUIMapper(To map mapped API response(domain model), to UI model) to the ViewModel. We use the mapped model(from API response to domain model) via MoviesMapperdirectly, which we passed to MoviesApiImpl.


Update 07/2020:

43- According to Arkadii Ivanov’s comment, I’ve changed the definition of ViewModelBinding to extends DisposableScope:

 

Image for post

 

For more information, please take a look into this.

 

Update 08/2020:

44- After using Kodein as a DI/Service Locator library, I’m setting API_KEY in shared module -> DI class. A benefit of this, we’d not set this key multiple times in different platforms.

A few points we need to take into account:

  • We have to set jvmTarget to 1.8 in android and shared modules.
  • We use kotlin.native.concurrent.ThreadLocal on the DI class to prevent “Unexpected mutability issue” on Koltin-Native.
  • Changes to apply Kodein.

45- Done!

 

 

Result 📺

 

Image for post

 


Under Development 🏭

  • 🔘 Improve architecture.
  • 🔘 Add search box.
  • 🔘 Use Sqldelight to show how to use the database in Multiplatform apps.
  • 🔘 Improve Clean Architecture approach and separate modules with the power of gradle.
  • 🔘 Categorize dependencies.
  • 🔘 Add tests.
  • ✅ Use a DI framework or a Service Locator

References:

  • Targeting iOS and Android with Kotlin Multiplatform
  • Kotlin Multiplatform + Reactive
  • Clean Architecture example with Kotlin Multiplatform
  • Effective architecture for Multiplatform native development in Kotlin
  • Mobile Kotlin multiplatform project template
  • Set up your first Kotlin Multiplatform project for Android and iOS
  • Mobile Kotlin multiplatform project template
  • Set up your first Kotlin Multiplatform project for Android and iOS

 

 

Tags: Android,iOS,Kotlin Multiplatform,Rxjava,Kotlin

 

View original article at: 


 

Originally published: July 27, 2020

Android News
Our Engineering Roadmap
Our Engineering Roadmap

By Mark Ng

We just completed our engineering road map for our Android apps at Australia Post. Each year we get together and try to decide on what we are going to do from an engineering perspective for the next 12 months. Each team gets to decide on what should be done now, what they want to complete by the end of the year and whats on the horizon for next year.

By ProAndroidDev -
Android News
Android Activity Lifecycle considered harmful
Android Activity Lifecycle considered harmful

By Eric Silverberg

The Android activity lifecycle is one of the first architectural concepts learned by a new Android developer. Ask any Android dev and they’ll likely all have seen this handy diagram from Google: 

By ProAndroidDev -
Android News
Our Safe Approach to Android Jetpack Navigation in a Multi-Modular App
Our Safe Approach to Android Jetpack Navigation in a Multi-Modular App

By Alejandro Weichandt

It has been a year since we started working on the Android version of the mobile app at Sync. During that year, we faced more than once that moment when we had to choose which path to follow on an Architectural decision. This story is about Navigation.

By ProAndroidDev -
Android News
Custom KotlinX Serializers
Custom KotlinX Serializers

By Jobin Lawrance

Let’s say we have a third-party class that we are using as a type in one of our data class that we want to be serialized, then we have to write a custom serializable for @Serializable to work.

 

By ProAndroidDev -
droidcon News

Tech Showcases,

Developer Resources &

Partners

/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/home-details/EmployerBrandingHeader
EmployerBrandingHeader
https://jobs.droidcon.com/
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/jobs-droidcon/jobs.droidcon.com
jobs.droidcon.com

Latest Android Jobs

http://www.kotlinweekly.net/
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/kotlin-weekly/Kotlin Weekly
Kotlin Weekly

Your weekly dose of Kotlin

https://proandroiddev.com/
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/pad/ProAndroidDev
ProAndroidDev

Android Tech Blogs, Case Studies and Step-by-Step Coding

/detail?content-id=/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Zalando/Zalando
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Zalando/Zalando
Zalando

Meet one of Berlin's top employers

/detail?content-id=/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Academy for App Success/Academy for App Success
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Academy for App Success/Academy for App Success
Academy for App Success

Google Play resources tailored for the global droidcon community

Follow us

Team droidcon

Get in touch with us

Write us an Email

 

 

Quicklinks

> Code of Conduct

> Terms and Conditions

> How to hold a conference

> FAQs

> Imprint

Droidcon is a registered trademark of Mobile Seasons GmbH Copyright © 2020. All rights reserved.

powered by Breakpoint One