SwiftUI

Announced at WWDC last week, SwiftUI is a new declarative UI framework that is described as an “innovative, exceptionally simple way to build user interfaces across all Apple platforms with the power of Swift.”. This approach to UI development has been popularised recently with emergence of Flutter, something that was also likely the inspiration for Jetpack Compose which was announced at Google I/O a few weeks back (SwiftUI appears to be at a significantly more advanced state of maturity than Jetpack Compose and is available to try out in Xcode 11 beta…but also important to note that it does require iOS 13).

Kotlin Multiplatform

Kotlin, unveiled by JetBrains in 2011, was initially a language targeted for the JVM. At I/O 2017, Google announced support for Kotlin for Android development and since then it’s popularity has exploded…galvanised further with the “Kotlin first” announcement at Google I/O 2019.

In recent times there’s also been an increasing interest in the Kotlin Multiplatform project and the opportunities it provides for having shared common Kotlin code running on multiple platforms…and, in particular, Kotlin/Native which allows the compilation of Kotlin for non-JVM platforms such as iOS. The feeling seems to be that this enables a more “palatable” level of code reuse (for example shared repository and data model) than say that espoused by “Cross Platform” frameworks where approach is to try and develop apps for multiple platforms from single code base (including UI). It’s worth noting that Kotlin has also become increasingly popular for developing back end services and I believe there’s also good opportunities in that context for sharing Kotlin code between client and server.

Galway Bus App

The GalwayBus project was created about 18 months ago, initially to allow exploring use of Kotlin and also the emerging Architecture Components for the development of Android applications. This became a platform then for trying out other technologies/libraries (e.g. migrating from Dagger to Koin and RxJava to Kotlin Coroutines)….and, in context of this discussion, having common Kotlin code running on Android and iOS. Kotlin Multiplatform is an area that’s still very much in development and I’d had somewhat limited success until about a week ago (just before WWDC as chance would have it!) when the stars aligned and the versions of various tools/libraries being used started working happily together!

The shared Kotlin/Native code for this made use of:

And, then, SwiftUI emerges!

So, finally, we get to point of the post! A day after my tweet above I watched the announcement of SwiftUI at WWDC and thought that the combination of this and shared Kotlin/Native code (for, at least in this case, shared repository and data model code) looked very interesting! I downloaded XCode 11 beta and a couple of iterations later this is what I had…certainly a much simpler/cleaner way of creating a list UI like this (compared to using likes of storyboards, UITableView etc)

struct ContentView : View {
    @EnvironmentObject var busRouteViewModel: BusRouteViewModel

    var body: some View {
        NavigationView {
            List(busRouteViewModel.listRoutes.identified(by: \.timetableId)) { route in
                RouteRow(route: route)
            }
            .navigationBarTitle(Text("Routes"), displayMode: .large)
            .onAppear() {
                self.busRouteViewModel.fetch()
            }
        }
    }
}


struct RouteRow : View {
    var route: BusRoute
    
    var body: some View {
        HStack {
            Image("ic_bus")
            
            VStack(alignment: .leading) {
                Text(route.timetableId).font(.headline)
                Text(route.longName).font(.subheadline)
            }
        }
    }
}

The listRoutes data was obtained using call, in Swift, to Kotlin GalwayBusRepository code. Note how the Swift closure provided maps cleanly to Kotlin lambda.

class BusRouteViewModel: BindableObject {
    var listRoutes: [BusRoute] = [] {
        didSet {
            didChange.send(self)
        }
    }
    
    var didChange = PassthroughSubject<BusRouteViewModel, Never>()
    
    let repository: GalwayBusRepository
    init(repository: GalwayBusRepository) {
        self.repository = repository
    }
    
    func fetch() {
        repository.fetchBusRoutes(success: { data in
            self.listRoutes = data
            return KotlinUnit()
        })
    }
}

Following is excerpt from Kotlin GalwayBuyRepository class. The suspend version of fetchBusRoutes is used by Android code….while other one is called from iOS Swift code (use of Coroutines in Kotlin/Native code is still something I’m getting my head around so probably cleaner way of doing this).

class GalwayBusRepository {

    private val galwayBusApi = GalwayBusApi()

    suspend fun fetchBusRoutes(): List<BusRoute> {
        val busRoutes = galwayBusApi.fetchBusRoutes()
        return transformBusRouteMapToList(busRoutes)
    }

    fun fetchBusRoutes(success: (List<BusRoute>) -> Unit) {
        GlobalScope.launch(ApplicationDispatcher) {
            success(fetchBusRoutes())
        }
    }
}

and following is the Kotlin BusRoute data class used (instances of which we’re able to effectively access directly in Swift code)

@Serializable
data class BusRoute(
        @SerialName("timetable_id")
        val timetableId: String,
        @SerialName("long_name")
        val longName: String,
        @SerialName("short_name")
        val shortName: String)

It’s also interesting to note expect/actual use of ApplicationDispatcher (used in call to launch() above). This is defined in commonMain as

internal expect val ApplicationDispatcher: CoroutineDispatcher

In androidMain

internal actual val ApplicationDispatcher: CoroutineDispatcher
			 = Dispatchers.Default

and in iosMain as

internal actual val ApplicationDispatcher: CoroutineDispatcher 
			= NsQueueDispatcher(dispatch_get_main_queue())

internal class NsQueueDispatcher(private val dispatchQueue: dispatch_queue_t) : CoroutineDispatcher() {
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        dispatch_async(dispatchQueue) {
            block.run()
        }
    }
}

Though, as alluded to earlier, use of GlobalScope above is far from ideal and means we don’t gain the main benefits of structured concurrency that Kotlin Coroutines provide.

And, lastly, this is what it looks like in iOS Simulator (I clearly won’t be winning any design awards here :) but this should at least illustrate how easy it is, with SwiftUI, to add UI on top of shared Kotlin code )

iOS Routes Screen

Code for this is available in the GalwayBus github repo.