KotlinNativeExperiments
This repo is a Kotlin Native playground.
I've followed the official guide to setup the iOS and the Android modules
This is the things that I wanna explore:
- Enums
- Enums with arguments
- Sealed classes
- Objects
- Nullability
- Companion objects
- Generics
- Higher-Order Functions and Lambdas
- Unit tests in the common module
- Distribution as library
- maven for the (android + common) as android library
- cocoapods for the (iOS + common) as framework
Enums
Enum definition
enum class SimpleEnum{
FIRST, SECOND, THIRD
}
Android
Into the android module I'm able to do all the common stuff that we are used to do with enums (access to the cases, iterate and count). So we can write a test that looks like
@Test
fun usageOfSimpleEnum() {
assertEquals(3, SimpleEnum.values().size)
SimpleEnum.values().forEach {
println(it)
}
}
iOS
Into the ios module I can access to the enum cases, but I don't know how iterate or get the enum size. So we can write a test that looks like
func testUsageOfSimpleEnum() {
print(KotlinLibrarySimpleEnum.first)
print(KotlinLibrarySimpleEnum.second)
print(KotlinLibrarySimpleEnum.third)
}
Enums with arguments
Enum definition
enum class EnumWithValue(val associatedValue: Int) {
ONE(1),
TWO(2)
}
Android
As in the previous section, even with the enums with argument, we can do everything we expect with an enum. So the test can be:
@Test
fun usageOfEnumWithValue() {
assertEquals(2, EnumWithValue.values().size)
EnumWithValue.values().forEach {
println("$it : ${it.associatedValue}")
}
}
iOS
As above, the same "issues" with the enum are still present even with the enums with values. At least we can access the value of the variable:
func testUsageOfEnumWithValue() {
XCTAssert(KotlinLibraryEnumWithValue.one.associatedValue == 1)
XCTAssert(KotlinLibraryEnumWithValue.two.associatedValue == 2)
}
Sealed classes
sealed class definition
sealed class SealedClassExample {
class Success(val successMessage: String) : SealedClassExample()
object Error : SealedClassExample()
}
Android
As usual into the android module we can do everything we expect with a sealed class. So the test can be:
@Test
fun usageOfSealedClass() {
val successMessage = "We are the champions"
val success: SealedClassExample = SealedClassExample.Success(successMessage)
assertSuccess(success, successMessage)
val error: SealedClassExample = SealedClassExample.Error
assertError(error)
when (success) {
is SealedClassExample.Success -> println("SealedClassExample.Success instance")
is SealedClassExample.Error -> println("SealedClassExample.Error instance")
}
}
private fun assertSuccess(sealed: SealedClassExample, expectedMessage: String) {
assertTrue(sealed is SealedClassExample.Success)
assertTrue((sealed as SealedClassExample.Success).successMessage == expectedMessage)
}
private fun assertError(sealed: SealedClassExample) {
assertTrue(sealed === SealedClassExample.Error)
}
iOS
As far as I know swift and objective-c have no concept of sealed classes. Therefore I do not think it is possible to have something similar to the when
of kotlin.
The tests we can have are like:
func testSealedClassUsage() {
let successMessage = "I'm the winner"
assertSuccessInstance(selead : KotlinLibrarySealedClassExampleSuccess(successMessage: successMessage), expectedMessage : successMessage)
assertErrorInstance(selead : KotlinLibrarySealedClassExampleError())
}
private func assertSuccessInstance(selead : KotlinLibrarySealedClassExample, expectedMessage: String){
XCTAssert(selead is KotlinLibrarySealedClassExampleSuccess,"Failed: sealed is \(String(describing: selead.self))")
let success = selead as! KotlinLibrarySealedClassExampleSuccess
XCTAssert(success.successMessage == expectedMessage,"Failed: message is '\(String(describing: success.successMessage))' the expected is '\(String(describing: expectedMessage))' ")
}
private func assertErrorInstance(selead : KotlinLibrarySealedClassExample){
XCTAssert(selead is KotlinLibrarySealedClassExampleError)
}
Objects
Object definition
object KotlinObject {
val INITIAL_VALUE_FOR_NULLABLE_STRING_PROPERTY = null
const val INITIAL_VALUE_FOR_INT_PROPERTY = 0
const val INITIAL_VALUE_FOR_NULLABLE_ANY_PROPERTY = "any variable initial state"
var internalStateNullableString: String? = INITIAL_VALUE_FOR_NULLABLE_STRING_PROPERTY
var internalStateInt: Int = INITIAL_VALUE_FOR_INT_PROPERTY
var internalStateNullableAny: Any? = INITIAL_VALUE_FOR_NULLABLE_ANY_PROPERTY
}
Android
As usual, no surprises
@Test
fun usageOfKotlinObject() {
assertTrue(KotlinObject === KotlinObject)
}
iOS
Well, even here in the ios module the kotlin object is a singleton 🎉
KotlinLibraryKotlinObject()
give us always the same instance
func testKotlinObjectUsage() {
XCTAssert(KotlinLibraryKotlinObject() === KotlinLibraryKotlinObject())
}
Nullability
Reusing the object defined in the section above
Android
Once again, no surprises
@Test
fun usageOfKotlinObject_properties() {
playWithIntProperty()
playWithNullableAnyProperty()
playWithNullableStringProperty()
}
private fun playWithIntProperty() {
assertTrue(KotlinObject.internalStateInt == KotlinObject.INITIAL_VALUE_FOR_INT_PROPERTY)
KotlinObject.internalStateInt = 123
assertTrue(KotlinObject.internalStateInt == 123)
}
private fun playWithNullableAnyProperty() {
assertTrue(KotlinObject.internalStateNullableAny == KotlinObject.INITIAL_VALUE_FOR_NULLABLE_ANY_PROPERTY)
KotlinObject.internalStateNullableAny = 123
assertTrue(KotlinObject.internalStateInt == 123)
KotlinObject.internalStateNullableAny = "Now I'm a string"
assertTrue(KotlinObject.internalStateNullableAny == "Now I'm a string")
}
private fun playWithNullableStringProperty() {
assertTrue(KotlinObject.internalStateNullableString == KotlinObject.INITIAL_VALUE_FOR_NULLABLE_STRING_PROPERTY)
KotlinObject.internalStateNullableString = "Now I'm not null"
assertTrue(KotlinObject.internalStateNullableString == "Now I'm not null")
}
iOS
There's something weird happening
func testKotlinObjectUsage_properties() {
playWithIntProperty()
playWithNullableAnyProperty()
playWithNullableStringProperty()
}
private func playWithIntProperty(){
XCTAssert(KotlinLibraryKotlinObject().internalStateInt == KotlinLibraryKotlinObject().initial_VALUE_FOR_INT_PROPERTY) // #1
KotlinLibraryKotlinObject().internalStateInt = 123
XCTAssert(KotlinLibraryKotlinObject().internalStateInt == 123)
}
private func playWithNullableAnyProperty(){
XCTAssert((KotlinLibraryKotlinObject().internalStateNullableAny as! String) == KotlinLibraryKotlinObject().initial_VALUE_FOR_NULLABLE_ANY_PROPERTY)
KotlinLibraryKotlinObject().internalStateNullableAny = 123
XCTAssert((KotlinLibraryKotlinObject().internalStateNullableAny as! Int) == 123)
KotlinLibraryKotlinObject().internalStateNullableAny = "Now I'm a string"
XCTAssert((KotlinLibraryKotlinObject().internalStateNullableAny as! String) == "Now I'm a string")
}
private func playWithNullableStringProperty(){
XCTAssert(KotlinLibraryKotlinObject().internalStateNullableString == (KotlinLibraryKotlinObject().initial_VALUE_FOR_NULLABLE_STRING_PROPERTY as! String?)) #2
KotlinLibraryKotlinObject().internalStateNullableString = "Now I'm not null"
XCTAssert(KotlinLibraryKotlinObject().internalStateNullableString! == "Now I'm not null")
}
- the kotlin val defined as
const val INITIAL_VALUE_FOR_INT_PROPERTY = 0
is mapped ininitial_VALUE_FOR_INT_PROPERTY
with "initial" lowercase - the kotlin val defined as
val INITIAL_VALUE_FOR_NULLABLE_STRING_PROPERTY = null
hasNothing?
as type (doc as reference 😬). Xcode tell us that the kotlin val was mapped into avar initial_VALUE_FOR_NULLABLE_STRING_PROPERTY: KotlinLibraryStdlibNothing?
so, a cast toString?
seems to be inevitable
Companion Objects
Companion object definition
class Container(val state: String) {
fun getDecoratedState(): String {
return "${sharedVar ?: ""} $state"
}
companion object {
const val MY_SUPER_NICE_CONST = 12
var sharedVar: String? = null
}
}
Android
As usual, no surprises
@Test
fun usageOfCompanionObject() {
assertEquals(12, Container.MY_SUPER_NICE_CONST)
val firstContainer = Container("first")
val secondContainer = Container("second")
assertFalse(firstContainer === secondContainer)
Container.sharedVar = "Hello"
assertEquals("Hello first", firstContainer.getDecoratedState())
assertEquals("Hello second", secondContainer.getDecoratedState())
}
iOS
As we could see into the objects section, even the swift usage of a companion object is straightforward
func testCompanionObjectUsage() {
XCTAssertEqual(12, KotlinLibraryContainerCompanion().my_SUPER_NICE_CONST)
let firstContainer = KotlinLibraryContainer(state: "first")
let secondContainer = KotlinLibraryContainer(state: "second")
XCTAssertFalse(firstContainer === secondContainer)
KotlinLibraryContainerCompanion().sharedVar = "Hello"
XCTAssert(firstContainer.getDecoratedState() == "Hello first")
XCTAssert(secondContainer.getDecoratedState() == "Hello second")
}