04 Apr 2021 - 6 minute read
If you’re starting a new Kotlin multiplatform mobile project, I think starting with ktor for your networking layer is a reasonable choice. If you’re planning to add Kotlin multiplatform to an existing project, this may not be desirable for a couple of reasons:
- Your application may already have a working networking layer, which itself may contain logic related to your API (e.g. authentication). Going with ktor means repeating or refactoring that logic in the shared multiplatform layer.
- ktor will manage its own platform-specific networking client (e.g.
URLSession
on iOS). If your ktor-based networking stack and your application’s “legacy” stack are making connections to the same hosts, you may end up with kept-alive connections to the same hosts in the connection pools managed by each stack (doubling your connections, depending on your platform).
Depending on your project, these issues may or may not matter to you, but with these ideas in mind, we (my team at Autodesk) took a stack-agnostic approach to multiplatform networking. In our multiplatform layer, we have an interface
called Network
, and it allows us to make network calls to our backend API. Here’s a look at the interface
itself. It started as this core method and a collection of supporting classes, though it has grown over time:
Starting at the top, we have interface Network
, which defines a method for making a request to one of our API hosts, which are represented by an enum with cases for each of our internal API hosts (not shown here—each case maps to a host URI, which can change based on staging/production environment). The non-host parts of the request are represented by the Request
class, and then the completion lambda is called with the response, when the request is finished. Making this call returns a handle to the request in the form of NetworkDisposable
(definition omitted), which is an interface
with a single method that can be called to cancel the request. Below that, you can see a sampling of the request and response classes. I left some definitions out for brevity, but hopefully you get the idea: a simple collection of classes to represent a basic HTTP request and response.
With these, you have each client application, who wants to use functionality of the multiplatform layer requiring network access, provide an implementation of Network
. So instead of ktor, you just bring along a class that implements this interface
and negotiates calls to your existing networking stack. In the PlanGrid app, we got started with this interface about two years ago, and it continues to serve us well, though we have added to it over time to support more use cases (e.g. downloading files). Here is a short list of things I like about it, which we take advantage of in our code:
- With all of our multiplatform business logic that needs network access using
Network
, it’s easy to writeNetwork
fakes in our tests that work across platforms. - We have a helper function that allows making a suspend-y network call. It’s an internal extension function on
Network
(using this) that allows internal code to useNetwork
with coroutines, while on the outside (back on your native iOS/Android/Windows platform), all clients get an easy-to-support lambda-based API. - It doesn’t make our decision on ktor final. With
Network
as this seam we have throughout our code, we can one day decide the time is right to swap out all of our platform-specificNetwork
implementations for a single one based on ktor.
Like I said earlier, ktor may be the right choice for your project, but our choice to take this interface Network
approach allowed us to get going on building the meat of our multiplatform business logic, fast. We continue to reap its benefits years later.
Questions or comments? Find me on mastodon.social.
Find a mistake or inaccuracy? Please open an issue or PR on github.