⏱ Reading time: 7 min

We at MOLO17 are always looking for new technologies that can boost our productivity and allow us to deliver software at its best. Since 2017 we started adopting Kotlin with profit as main language for developing Android applications. After a huge project entirely written with JetBrains’ language, I personally started believing that this new technology is going to introduce a breeze of innovation in the modern software development.

Recently, I invested my time studying the Kotlin Multiplatform feature: a powerful tool that combines Kotlin/JVM (which compiles into JVM bytecode), Kotlin/JS (which compiles into Javascript) and Kotlin/Native (which compiles into low-level binaries) together allowing a developer to write a common codebase entirely in Kotlin, specialize it with some platform-dependent implementations, and ship a software which runs on the JVM (or Android), on the web and, why not, in your iPhone. And when I say that runs, I mean that runs absolutely well.

Cool, isn’t it?

Some of you may argue that this is yet another way to write a cross-platform mobile app, like the Cordova or Ionic frameworks using JS, or Xamarin using C#.
But this is not what the people behind Kotlin MPP meant when designing this technology.
Just by looking on how the project will be structured by following the JetBrains’ guidelines, we can state that the principle behind this kind of multi platform concept is code sharing, made possible by defining in a common module the Kotlin pure definitions and implementations of entities and classes. In this module, all your business logic, presentation logic and – with the right libraries – data-access logic will take place. The rule of thumb is always to be framework-agnostic.
The Kotlin Academy Portal application developed by Marcin Moskala is just one of the proofs of the feasibility of these intentions (https://github.com/MarcinMoskala/KtAcademyPortal).

Completely amazed by that features, we at MOLO17 started a R&D department dedicated to the multi platform development.
As a Software Engineer with a main focus on the Android platform, I started some projects for mastering the Kotlin MPPs.

The project

The first project I developed was a simple Bluetooth mobile library, for the Android and iOS platforms: the goal was to write a common API for both the environments, for a simplified access to the Bluetooth framework.  
I started by creating the project following the documentation at Kotlin’s website. Pretty easy to setup and get running. The cool thing I really appreciated is that we can create a MPP directly from Android Studio! So no need to install IntelliJ (which I personally already have, but if you don’t, you don’t need it for this purpose).
The first-step expectation was an API that allowed the developer to start the discovery of BLE peripherals. So I started defining the common interface for this class, something like this:

expect class BluetoothAdapter {
    fun discoverDevices(callback: (BluetoothDevice) -> Unit)
    fun stopScan()
}

Simple.  

Notice the expect keyword at the beginning of the class declaration. In the Kotlin MPP context, the expect keyword defines an entity (a class, a method, a property, or even a constructor) that the developer is declaring in the common module and is expected to be present in the target platforms. By “expecting” such entity, the developer needs also to “actualize” it in the platform-dependent module, using the actual keyword. A sort of interface-implementation paradigm, but more dynamic. And here comes the sugar.

Android Target

When targetting the Android platform, nothing’s different with the implementation you’ll do with a standard project approach. You need our lovely god Context object, from which you get a BluetoothManager and then you can access to the system BluetoothAdapter instance.
A powerful feature on the expect/actual paradigm is that if you expect a class, you’re not required to expect also the constructor of that class. So in my case, I was able to create the actual definition that required a Context instance in the constructor, which allowed me to implement the definition as I wanted to.

actual class BluetoothAdapter(
    private val context: Context
) : ScanCallback() {

    // ...
    
    actual fun discoverDevices(callback: (BluetoothDevice) -> Unit) {
        this.callback = callback
        bluetoothAdapter.bluetoothLeScanner.startScan(this)
    }

    actual fun stopScan() {
        bluetoothAdapter.bluetoothLeScanner.stopScan(this)
        callback = null
    }
}

iOS Target

More sugar comes when targetting the iOS platform. Apart from the benefits you get by not using Xcode, you get other advantages by using Kotlin as iOS programming language. The Kotlin lib offers bridges classes which translates the Obj-C system APIs (like CoreBluetooth in our case) in Kotlin classes. That allowed me to implement the iOS “actualization” of my Bluetooth class using CB, writing in Kotlin. Awesome.

I instantiated the CBCentralManager as usual, I set a CBCentralManagerDelegate instance to it and started receiving the discovered peripherals. Notice also that when translating protocol to Kotlin, the compiler adds the Protocol suffix to the base name.

At first, I wanted to make my actual class named BluetoothAdapter implement the delegate protocol directly. Hence, I did it. But since the CBCentralManagerDelegate implements the NSObjectProtocol, I was required to let my actual class implement also the methods defined by this protocol. To avoid to implement them, I let the class extend from NSObject, for having the required methods already implemented. All went well until I tried to use my class definition into a Swift project. The compiler failed with the following error:

'BluetoothAdapter' is unavailable: Kotlin subclass of Objective-C class can’t be imported
Xcode compiler error due to unavailability of BluetoothAdapter
Xcode compiler error due to unavailability of BluetoothAdapter

Obj-C headers

By looking into the Obj-C generated headers, I saw that my BluetoothAdapter class was correctly compiled but marked as unavailable with the above message, and so I was not able to instantiate a new one or even define it as property type.
After some googling, I figured out that this behavior is a wanted limitation for avoiding potentially complex inheritance trees with the Foundation classes on the Kotlin side (according to the never-trust-anyone philosophy, I must say).
Obtaining an instance of my class then was not possible. I declared in Kotlin a function that returned and instance of my type, and when compiled the return type became NSObject. Hence I wrote down my two cents.

In a nutshell, we can use a class which inherits from Foundation objects only from the Kotlin side, and if we need to create an instance of it from Swift code, we need to create a builder function in Kotlin, because Kotlin knows how it’s defined and how to create it. Something like this:

fun makeBluetoothAdapter() = BluetoothAdapter()

actual class BluetoothAdapter: NSObject() { ... }

And then in the Swift implementation:

private let myClass = ConsumerClass(
    bluetoothAdapter: BluetoothAdapterKt.makeBluetoothAdapter()
)

Our Kotlin codebase will know the type of the instance we pass with the bluetoothAdapter label, and all will work correctly.

Now, let’s go back to my original purpose. I left my actual class without any extension, and I eventually created an object property subclassing NSObject and implementing the CBCentralManagerDelegate protocol.

actual class BluetoothAdapter {

    // ... 

    private val delegateImpl = object : NSObject(), CBCentralManagerDelegateProtocol {
        override fun centralManager(
            central: CBCentralManager,
            didDiscoverPeripheral: CBPeripheral,
            advertisementData: Map<Any?, *>,
            RSSI: NSNumber
        ) {
            // ... 
        }
    }


    private val manager = CBCentralManager().apply { delegate = delegateImpl }

    actual fun discoverDevices(callback: (BluetoothDevice) -> Unit) {
        // ...
        manager.scanForPeripheralsWithServices(null, null)
        onDeviceReceived = callback
    }

    actual fun stopScan() {
        manager.stopScan()
        onDeviceReceived = null
    }
}

And here we go! A common class and the platform-specific implementations.
Following the JetBrains’ guidelines, I was able to add as Build Phase a script which launches the Gradle task for producing the Cocoa framework file, embeddable into a Xcode project.

I developed two simple apps, Android with Kotlin and iOS with Swift, that show a list of the discovered devices using the common module. The result was great: the Bluetooth discovery worked flawlessy as if it was implemented in the standard way.

The following step was the integration of a standard BLE protocol communication… Presented in the next part of this blog post! Thanks for reading!

Cover image credits: blog.jetbrains.com