avatarFederico Torres

Summary

The article proposes an architecture for building Kotlin Multiplatform Mobile (KMM) apps, aiming to share both business and presentation logic between iOS and Android platforms.

Abstract

The proposed architecture for Kotlin Multiplatform Mobile (KMM) apps aims to minimize platform-specific code and share logic between iOS and Android. The architecture includes a Kotlin shared code module containing core logic, iOS and Android modules for the respective apps, and an "Arch" library for architecture support. The Arch library is a lightweight Kotlin multiplatform library that simplifies mobile application architecture, inspired by Spotify's Mobius library but using coroutines, SharedFlow, and StateFlow. The architecture follows a unidirectional flow, simple state management, and supports Jetpack's ViewModel. The article demonstrates the implementation of a simple movies app using this architecture and the movie db API.

Opinions

  • The proposed architecture aims to minimize platform-specific code and share logic between iOS and Android platforms.
  • The architecture uses a unidirectional flow and simple state management.
  • The Arch library is a lightweight Kotlin multiplatform library that simplifies mobile application architecture.
  • The architecture supports Jetpack's ViewModel.
  • The architecture is demonstrated through the implementation of a simple movies app using the movie db API.
  • The architecture is a good fit for teams maintaining big native apps for iOS and Android, especially those with developers knowledgeable in both platforms.
  • The architecture allows developers to work on both platforms easily and distribute tasks more efficiently within the team.

Kotlin Multiplatform Mobile and how to share ViewModel: An architecture proposal

Source: https://kotlinlang.org/lp/mobile/

We will review a framework for building KMM apps with the objective of not only share business logic but also presentation logic. Kotlin shared code will contain the core logic while from the client side (iOS and Android) we will have very few code, just Compose UI and SwiftUI.

TL;DR If you want to go directly to the code, here is the demo code of my proposal which has been added to the official list of KMM samples

What do we want to achieve?

  • Share business and presentation logic following a unique architecture on both platforms.
  • Minimize iOS and Android specific code
  • Observe state changes from Compose UI / Swift UI and react to them

What is KMM?

If you are reading this article, I will assume you already have a notion of what KMM is, in which case you can move on to the next section. Otherwise let’s make a quick review.

From its official web:

Kotlin Multiplatform Mobile (KMM) is an SDK designed to simplify the development of cross-platform mobile applications. You can share common code between iOS and Android apps and write platform-specific code only where it’s necessary. For example, to implement a native UI or when working with platform-specific APIs.

How does it work?

Source: https://kotlinlang.org/lp/mobile/

Kotlin shared code is compiled to JVM bytecode on Android and to native binaries on iOS, so on Android we just add a Gradle dependency and on iOS we just link a framework.

Why use KMM?

  • Write your app’s logic just one time, less code means less potential bugs.
  • Unlike other cross platform solutions, we don’t lose performance and we are able to use all the native libraries made for Android and iOS
  • Using the actual/expect protocol we can write platform specific code if needed, for example we will use this feature to be able to inherit our custom ViewModel class from Jetpack’s ViewModel

Proposed Architecture

  • iOS and Android: these modules contains the iOS and Android apps (Xcode and Android Studio projects). Most of the code should be only SwiftUI and ComposeUI code.
  • Kotlin shared code: this is the module containing the KMM project, here we will implement our business and presentation logic. So the core of our app will be here.
  • Arch: this is a KMM library made by me which we will add as a dependency to our shared code.

Arch library

Arch is a small and lightweight Kotlin multiplatform library that helps us to architecture mobile applications, it is based on Spotify’s Mobius library but instead of relying on RxJava, it relies on coroutines, SharedFlow and StateFlow

We will review the core concepts of the library, I will omit some topics like Events and error handling just for simplicity.

Key features:

  • Unidirectional flow
  • Simple state management
  • Support for Jetpack’s ViewModel (by using the actual/expect protocol)

App´s flow using Arch

This diagram shows how the components in this architecture interact between them, let’s review each one of them and give a brief explanation:

  • Actions: an action is a request to modify the state of the application (or a part of it), actions are dispatched from iOS and Android code.
  • Updater: you can imagine the updater as a pure function which will receive an action and the current state, and will return a new state. Besides modifying the state, Updaters can dispatch SideEffects and Events
  • SideEffects: generically speaking, a side effect is an operation that modifies some state outside its local environment, in our architecture SideEffects are operations like a HTTP request, a database transaction or any I/O operation.
  • Processor: the processor is the element that process SideEffects , it is in charge of executing the database transaction, http request or whatever the received SideEffect should do. When the task is completed (successfully or not) the processor will dispatch a new Action in order to update the state.

Writing a demo app

Let’s dive into real code, we will write a very simple movies app with this proposed architecture using the movie db api

Steps

  • Define State
  • Define Actions
  • Define SideEffects
  • Create ViewModel
  • Implement Updater
  • Implement Processor
  • Observe state changes from SwiftUI and ComposeUI views.

Except for the last one, all these steps are implemented inside our shared kotlin module.

State

For this app we just need to save two things in our state:

  • The list of movies
  • The selected movie, since we want to show the details of a movie when the user taps it from the list.

Actions

we just need three actions:

The first two actions will be dispatched by user events (like tapping or opening the app), the last one will be dispatched by the Processor when we get the response from the API.

SideEffects

We have a single SideEffect: get the list of movies. We could set on which dispatcher or coroutine scope we want to run our SideEffect, by default it will use the viewModelScope on Android and a custom scope on iOS

Updater

Here we decide how we want to modify the state based on the received action, we have to create a class implementing the Updater interface, which has only one method that returns a new state and (maybe) new SideEffects wrapped inside a Next object.

Notice that fetchMovies() method is returning a NextResult object which contains a SideEffect, particularly the LoadMovies one. So the Processor will get notified of this new dispatched SideEffect and will execute the HTTP request operation

Processor

Now we have to implement the Processor interface which has a unique suspend function that returns an action.

TMDApi class is in charge of making the HTTP request for getting the movies and converting the json response into an array of movies, we don’t care how it is implemented but if you are curious you can always check the full demo code repository, under the hood it is using Ktor.

After we get the list of movies, we just dispatch the action to save them to the state.

ViewModel

Almost there, now we have to wire up all these classes that we have implemented via the ViewModel which must inherit from ArchViewModel

Yes! that is all the code our ViewModel needs.

Note that we have set an initial state and an initial effect.

Observe state changes

Final step, just observe state changes! but how?

ArchViewModel exposes a Flow which emits all state changes, so we just have to collect it.

On Android collecting a Flow is straightforward but on iOS it is not that simple, that is why the Arch library implements the following FlowWrapper

If you have a better solution to this problem of collecting Kotlin Flows from Swift code, I’d love to read it.

Let’s see the final code:

And that’s all! remember that the full code is here

So what are the drawbacks?

From an Android developer perspective, the development experience using KMM (and this approach) is pretty much the same as just developing pure Android. But for iOS developers the experience is not as good as for Android developers, on iOS we have to take care of stuff like concurrency, InmutabilityException and other incompatibility issues that may arise.

Jetbrains is aware of these problems and is working on improving the experience for iOS developers, on August 31, 2021 they released the preview version of the new memory manager that should free us from the need of freezing objects.

Conclusions

This architecture is a great fit for teams who maintains big native apps for iOS and Android, specially if the developers on your team have knowledge on both platforms.

Usually iOS and Android versions of the same app have different architectures like MVC, MVP, MVVM, VIPER or Redux, keeping a same architecture on both platforms has a lot of advantages, requires less work, and helps to better distribute the tasks among your team, developers can even work on both platforms very easily.

Overall I think there is no perfect solution, each team should consider which is best suited to their needs.

Android
Viewmodel
Kotlin Multiplatform
AndroidDev
Android App Development
Recommended from ReadMedium