A pattern for code sharing in Kotlin Multiplatform

In this article, Douglas Hoskins shows how the MVP pattern can be used to great effect when creating a robust, maintainable cross-platform app with Kotlin Multiplatform.

With Kotlin Multiplatform we can create a library of shared business logic and use it in our iOS and Android apps. It’s possible to use any class in that library, anywhere within our native code. Sounds powerful! 

But in reality, we need to be more organised.

We need a pattern for architecting our system which will:

  • clearly define the boundaries of our shared codebase
  • make the shared code more structured and easier to develop
  • make it easier for the entire development team to work with shared code
  • make the shared code easier to test

The MVP pattern works really well in Kotlin Multiplatform apps for maximum code sharing in a structured fashion. It lets us clearly define our interfaces to our shared library. This makes native implementation simpler, and makes testing a breeze.

In this blog we are going to see how to create a simple weather display screen in the MVP pattern.

Let’s get started!

The View

As we can see from the diagram, the Presenter and Models components of our pattern live in the shared Kotlin code, while the Views are native.

But in order for the pattern to work on shared code, the Views need to be the same on both platforms. So let’s start by creating an interface for our native View.

This interface encapsulates anything the presenter would want to send to the view. Don’t forget that the shared logic should be doing all of the heavy lifting. The views should be “dumb”: data passed into the view should be ready to display to the user without further processing.

The next step is to implement that view on the iOS and Android native sides.

Let’s look at iOS first. Kotlin Native maps Kotlin interfaces to protocols which can be accessed from Swift or Objective-C.

The iOS view is in place, and we’re making use of a Kotlin interface from Swift. Very nice! The Android side is more predictable:

Nothing out of the ordinary here, we declare our Activity in Kotlin and implement the View interface in the usual manner. Life with Kotlin Multiplatform is very easy for Android developers!

Our native views are in place. We can see that specifying our views as Kotlin interfaces works really well because:

  • The only points of interaction between shared and native code are those defined in the contract
  • iOS and Android developers can easily see what they need to do to interact with shared code (by implementing the functions we have defined)
  • Changing our view interface will cause a compilation error on our native views. We can be sure that the native side is up-to-date with the shared code at all times

Now let’s see how to implement a Presenter that can be used by our native views.

The Presenter

We’ve specified an interface for our View already, so now let’s do the same for our Presenter. 

This completes our WeatherContract we defined earlier, with a specification of both the View and the Presenter. It’s pretty simple: we have functions to associate and dissociate the view with the presenter, and another function to refresh the weather (possibly to be triggered by a refresh button or a Pull-to-Refresh mechanism).

Specifying an interface for the presenter will help us immensely when it comes to mocking it for unit testing.

For now, let’s create a real implementation of the presenter. 

The presenter’s job here is to accept actions performed by the user and ensure the correct app functionality happens as a result. In this case we have abstracted location and weather retrieval into dependencies that are provided to the presenter in the constructor.

This will help us with testing as we’ll see later. But for now we’ve provided default implementations of them, giving us a concrete presenter which we can instantiate from our native Views and hook up to our UI.

Let’s look at iOS first.

We instantiate our presenter in viewDidLoad, and pass it a reference to the view. Additionally, we call presenter.dropView() in our dealloc method — this is the presenter’s chance to do any cleanup required when the user navigates away from the screen. 

However, in order for this method to get called, we need to ensure the presenter is storing a weak reference to our view, not a strong reference.

If we don’t do this, then we will have a memory leak. The presenter’s strong reference to the view will prevent the view from being deallocd.

Both iOS and Android support weak references, but Kotlin Multiplatform has no built-in notion of them. We need to add it ourselves. Fortunately this is very easy!

First of all we declare a WeakReference in shared code:

The expect keyword here tells us that the implementation of this class will be provided separately for each target platform.

We then need to implement it separately for iOS and Android using actual.

Here’s what it looks like for Android:

And on iOS:

That’s it – we now have a WeakReference definition we can use from our shared code. So let’s edit our Presenter implementation to use it:

Very nice! We are now safely storing a weak reference to our iOS native view.

Finally let’s add the presenter to our Android view:

That’s all we need! We now have a functioning MVP interface for our native views to communicate with the presenter.

Testing

The final piece of the puzzle is testing. Kotlin Multiplatform forces us to keep our view and business logic code cleanly separated, and therefore much easier to test. We can use the kotlin.test library with MockK to simulate and test all the scenarios we care about.

Here’s an example of a test which ensures the happy path behaves correctly and ensures that values make their way from the location and weather providers to the native views.

This approach can be extended to cover a wide variety of success and error scenarios. 

The MVP pattern means our views are very lightweight, so the majority of our testing effort should be directed towards testing our shared code like this. Future articles will also look at snapshot and interaction tests for our native views.

Wrap up

In this article we’ve seen how to use MVP to specify contracts between native and shared code. Using this pattern, it’s very clear exactly how a shared library should be used from iOS or Android code. It scales well for simple or complex screens and can be used across your project.

We’ve also seen how to use the expect/actual mechanism to avoid memory leaks when using this pattern on iOS.

And we have seen how our presenter logic can be tested, adding confidence and robustness to our shared code and providing a high level of test coverage to both our iOS and Android apps.

This approach puts us well on our way to creating a shared codebase which will make our iOS and Android apps more consistent, and reduce the effort required for development, testing and maintenance.

In future articles, we will look more closely at the presenter implementation, and at some of the interesting libraries available for use in Kotlin Multiplatform projects.

Introduction to Kotlin Multiplatform

We’ve just finished building our first commercial project using Kotlin Multiplatform. In the first of an ongoing series Douglas Hoskins, our Head of Mobile, gives an overview and some initial thoughts. We’re already working on future posts that will go into more detail and explain how we used this emerging technology.

Mobile development in 2019. The choice for developers is greater than ever in terms of frameworks and architecture.

But there’s one approach in particular that is getting the team at Future Platforms very excited indeed: it’s Kotlin Multiplatform.

Kotlin has been the language of choice for the modern, discerning Android developer for some time now. Its impact on the scene has been similar to that of Swift in the iOS community.

It’s been a breath of fresh air for those used to Java. Verbose, boilerplate-heavy code has made way for powerful, expressive Kotlin. It makes codebases more readable, and its null-safety eliminates entire classes of bugs. Android developers have embraced it in droves.

As the name suggests, Kotlin Multiplatform is a framework which allows Kotlin code to run on many target platforms.

This includes iOS!

It’s been around in beta form for quite a while, and it’s now ready for production. In fact, we’ve just finished a large project using it.

This post will introduce the platform and explain why we think it’s a great approach for cross platform app development.

Let’s set the scene. You should seriously consider Kotlin Multiplatform if:

  • You are building apps for both iOS and Android
  • Which are equivalent in terms of functionality and user journey
  • But the UI should still reflect the finer nuances of the target platform

This brings us to the first key thing to understand about Kotlin Multiplatform: there is no shared UI. The only shared code is behind the scenes; your app’s back-end.

You’re essentially building a bespoke core library for your native iOS and Android apps to share.

This means all sorts of complicated business logic can be written once, instead of twice. Think of everything that goes on below an application’s UI: network calls, user input validation, data manipulation, storage and caching.

With the traditional model of separate development teams per platform, this logic is designed and implemented twice, once by an iOS team, once by an Android team. Inevitably, both will have their own quirks, their own slightly different interpretations of business requirements, and their own bugs.

Using Kotlin Multiplatform, both apps are aligned on a single standard library. This increases stability, reduces development effort and streamlines testing.

But wait, don’t other solutions provide a means of sharing UI too?

They do, and Kotlin Multiplatform steps away from this thorny issue. I won’t dive into all the pros and cons of cross-platform UI frameworks here, but it’s fair to say they all come with some amount of risk. So while some may consider this a shortcoming, Kotlin Multiplatform in fact offers a pragmatic solution for low-risk code sharing.

Code you write in a Kotlin Multiplatform library really behaves the same as equivalent code you’d write for the native platform alone. And your apps are no different from the point of view of the user.

From the developer’s perspective, the latest and greatest tools can be used to craft pixel-perfect user experiences, tailored to the platform.  Native teams can use their existing skills. And if you’re building a team, Kotlin and Android already go hand in hand.

Speaking of teams… what about the iOS developers? How did they cope with a considerable change to the regular app development workflow?

Our iOS folks all reported positive experiences with Kotlin. I think there’s a few reasons for this. For one, people really love to learn something new. New technologies, new approaches. It also brought the team closer. iOS and Android developers were collaborating, working together to create the best possible API for the shared code, that would work on both platforms.

But mainly, the benefits were easy to see by the whole team. Each new feature added to the app brought additional complex back-end logic, implemented only once. Less code to write means less code to maintain and debug in future; everyone’s happy!

Finally, and this may be controversial so I’ll whisper it, Kotlin’s actually a lot like Swift, as this cheatsheet shows! Most concepts Swift developers are used to have easily recognizable equivalents. It really isn’t difficult for an iOS team to pick up.

What is the development experience like?

Android Studio and IntelliJ are both supported for Kotlin development. This means Android developers can work entirely in one environment, but iOS developers who want to work on Kotlin code will need two IDEs side-by-side.

In fact, the story is very straightforward on Android — the shared codebase behaves like any other Kotlin library you might include in your project.

On iOS it’s also pretty simple; you build your shared Kotlin library, and then it becomes available for use in your Swift code the same as any other library you are importing.

Any class defined in your core Kotlin codebase can be accessed or instantiated from iOS or Android native code. Sticking to a pattern such as MVP keeps things tidy and organised.

What can you do in a Kotlin Multiplatform shared codebase?

The flavour of Kotlin that you write in a Kotlin Multiplatform codebase is slightly different to what you might find in an Android codebase. The main reason is that you can’t use anything provided by the JVM, or by the Android framework itself.

But fear not! Kotlin provides a rich standard library, while HTTP and JSON parsing are available via multiplatform libraries. These, combined with a growing selection of open-source libraries from the community, means that most things you’ll want to do in your iOS or Android app will be possible in Kotlin.

Of course, there is complete flexibility to fall back to native code when required. For example, in our project, the Klock library provided most of what we needed for date and time manipulation, but we still needed access to the iOS and Android native timezone utilities.

Kotlin’s native interop means it is also possible to write iOS-specific Kotlin code. This code can interact with the iOS platform APIs, meaning it is possible to write an entire iOS app only in Kotlin! While we would not recommend doing that, we did find this convenient when we needed to create very small snippets of code touching the platform APIs.

Anything to watch out for?

This technology is still emerging, so occasional issues are to be expected. And it should be noted that the iOS compatibility is still officially in beta. But, on the whole we found the stack reliable.

There is an official tutorial for getting started, along with many unofficial ones. Things tend to go well if these are followed to the letter! But if your setup has to deviate from the template for any reason, there is a very active and engaged Multiplatform community on the #kotlinlang Slack channel. This should be your first port of call in the event of any issues. The participants there have advice and solutions for most problems.

Some of the platform libraries are under heavy development, so frequent updates are available. A pragmatic approach is required to ensure that these don’t disrupt an existing, working setup.

We are very pleased with the results Kotlin Multiplatform has delivered for our teams so far. It will be at the top of our list of technologies for new projects, and we will be looking closely at deploying it into existing projects also.

Look out for more blog posts in the future which will explore the aspects of Kotlin cross-platform app development in greater depth.

Effective fully-native multi-platform app development

Hype and excitement around cross-platform app development has never gone away. It seems to go in cycles: some big industry player releases a new framework which creates a lot of buzz, then it dies down slightly, only for another new framework to gain traction.

They all seek to let developers maximise reach and minimise development costs.

Efficient and innovative cross-platform development has always been important to us at Future Platforms; the “Kirin” model – shared application code running on both iOS and Android (but with separate UI) – has been pivotal to complex apps we have delivered for Domino’s Pizza, Glastonbury and Wembley.

We have always placed a high value on the benefits of this approach: time and effort saved by not solving problems twice over, with associated reduced costs of ownership and maintenance. The approach also ensures a single, consistent interpretation of client requirements, and therefore an equally good user experience across platforms.

But choosing to use a cross-platform approach for your app can be a risky decision. Frameworks such as React Native and Flutter add an additional layer between the developer and the machine, making it harder to diagnose any issues you run into (is this a bug in the framework, or am I just using it wrongly?). They also require the use of languages which are not otherwise used on mobile; JavaScript for React Native, Dart for Flutter, and C# for Xamarin.

Continue reading “Effective fully-native multi-platform app development”