Kotlin Multiplatform: Building a “Fat” iOS Framework for iosArm64 and iosX64

If you are building a Kotlin Multiplatform library which will be consumed by an existing iOS application, using a framework is a great way to do this. Frameworks are typically compiled for a specific target architecture which can then be run on an iPhone, iPad or iOS Simulator on your Intel Macbook.

If you are going use your Kotlin Multiplatform library as a framework in an existing app, you will want to provide a “Fat” framework which will contain both Arm64 and X64 architectures. This article contains the configuration I used to build the “Fat” framework. This is not a full build.gradle.kts file, but just the additional parts needed for a Kotlin Multiplatform project to build a “Fat” framework.

⚠️Use an XCFramework instead ⚠️

This “Fat” framework method no longer works with XCode 12+. Use an XCFramework instead.


I ended up with this Error Message in XCode 12.4: “Building for iOS Simulator, but the linked and embedded framework ‘my_library.framework’ was built for iOS + iOS Simulator.”

This Stack Overflow post shows you how to create an XCFramework from your two frameworks, and I’ll follow up with a blog post on how to do it with an XCFramework a bit later. This solutions ends up combining the two frameworks into a single XCFramework.

xcrun xcodebuild -create-xcframework \
    -framework /path/to/ios.framework \
    -framework /path/to/sim.framework \
    -output combined.xcframework

Custom Gradle Task to Build “Fat” framework

import org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask

kotlin {
    // Set a name for your framework in a single place and reuse the variable
    val libName = "my_library"

    // Configure your Kotlin Multiplatform lib to generate iOS binaries
    // NOTE: This will only work on Macs
    ios {
        binaries.framework(libName)
    }

    // You can choose your output directory
    val frameworkDestinationDir = buildDir.resolve("cocoapods/framework")

    tasks {

        // Custom task to build the DEBUG framework
        // ./gradlew universalFrameworkDebug
        register("universalFrameworkDebug", FatFrameworkTask::class) {
            baseName = libName
            from(
                iosArm64().binaries.getFramework(libName, "Debug"),
                iosX64().binaries.getFramework(libName, "Debug")
            )
            destinationDir = frameworkDestinationDir
            group = libName
            description = "Create the debug framework for iOS"
            dependsOn("linkDebugFrameworkIosArm64")
            dependsOn("linkDebugFrameworkIosX64")
        }

        // Custom task to build the RELEASE framework
        // ./gradlew universalFrameworkRelease
        register("universalFrameworkRelease", FatFrameworkTask::class) {
            baseName = libName
            from(
                iosArm64().binaries.getFramework(libName, "Release"),
                iosX64().binaries.getFramework(libName, "Release")
            )
            destinationDir = frameworkDestinationDir
            group = libName
            description = "Create the release framework for iOS"
            dependsOn("linkReleaseFrameworkIosArm64")
            dependsOn("linkReleaseFrameworkIosX64")
        }
    }
}

Here are two custom gradle tasks that build a “Fat” framework for debug or release. In this I have it outputting to the build/cocoapods/framework directory, but you can configure that as you like.

Gradle Task for “Fat” iOS framework

  • Build a “Fat” debug version of the framework
    • ./gradlew universalFrameworkDebug
  • Build a “Fat” release version of the framework
    • ./gradlew universalFrameworkRelease

Importing the iOS Framework into XCode

I previously wrote a blog post about how to do this which has a companion video along with it. 👇

Thanks and Related Resources

I didn’t figure this all out myself. I just got it to work for me and extracted out the bare minimum you need to make this work. Thanks to Swapnil Patil for letting me know that “Fat” frameworks are possible. Thanks so much to Marco Gomiero for his post Introducing Kotlin Multiplatform in an existing project.

Run Custom Gradle Task After “build”

After running ./gradlew build on my Kotlin Multiplatform project, I wanted to copy the JavaScript build artifacts (.js & .html) to publish a demo where someone could test my library via a web browser. A custom Gradle task is is a great way to write your own behavior to accomplish this using the Gradle build system.

My Custom Gradle Task

I found the Gradle documentation for how to copy files, and was able to write this custom task "myTaskName" to do it. You can then just run ./gradlew myTaskName and it’ll run the task independently. The problem was that I didn’t know how to get it to always run after ./gradlew build ran.

/** Copies files from "build/distributions" to "demo" directory */
tasks.register<Copy>("myTaskName") {
    println("Copying Build Artifacts!!!")
    from(layout.buildDirectory.dir("distributions"))
    include("**/*.*")
    into(layout.buildDirectory.dir("../demo"))
}

TL;DR – Use finalizedBy()

  • finalizedBy – Runs my task AFTER “build”. ✅
    • tasks.named("build") { finalizedBy("myTaskName") }
  • dependsOn – Runs “build” BEFORE my task, if my task is executed explicitly.
    • tasks.named("myTaskName") { dependsOn("build") }

To give you more details and to go into my process of how I figured it out, here are a few options I tried while figuring out how to run this custom task after every execution of the “build” Gradle task.

1. finalizedBy ✅

You can use finalizedBy() to say what task you should run after a named task. I think this reads nicely because it calls out the task dependency separately from it’s declaration. This will appropriately run after the build task executes as I needed.

tasks.named("build") { finalizedBy("myTaskName") }

2. shouldRunAfter 🤔

Another possible way to do this is with shouldRunAfter() which can just be added inside the block where you register your custom task. This will run after the build task executes. HOWEVER: I had to call .get() after registering my task to get it to work in order to have this actually run, which just feels wrong… Someone feel free to explain why, but I’m guessing this is some sort of lazy initialization happening if I don’t call “.get()”. Because of this, I don’t like this solution personally.

tasks.register<Copy>("myTaskName") {
    shouldRunAfter("build") // Tells Gradle to execute after "build" task
    // My Custom Task Code
}.get()

3. dependsOn() 🙃

You would think dependsOn() may work, but it’s the opposite behavior than what I wanted. It’s saying that “myTaskName” needs “build” to have run before it executes. It’s the opposite task dependency relationship from finalizedBy(). This wasn’t the behavior I wanted for this use case because I wanted it to copy the artifacts after every time a ./gradlew build was run, but could be useful depending on your use case.

tasks.named("myTaskName") { dependsOn("build") }

Wrap Up

None of this is rocket science, but if you try to Google for how to do it, it may take you a while to figure out how to do it. Using Option 1, finalizedBy() was the solution that worked for my use case! I hope this saved you an hour or more!

Intro to Kotlin Multiplatform JavaScript

One of the compilation targets of Kotlin Multiplatform is JavaScript. It’s pretty awesome that I can take the same Kotlin code and tools I write Android with, and write code that runs in a browser (or in Node.js).

Here is the video I’ve created to walk you through the creation of a Kotlin Multiplatform Project using the templates in Intellij IDEA, and what you can do to get your Kotlin running as JavaScript in the Browser!

Here are the code snippets mentioned in the video:

./gradlew build
 js(LEGACY) {
     browser {
         webpackTask {
             output.libraryTarget = "this" // Will add to window
         }
         binaries.executable()
     }
 }
 fun printHi() {
     println("Hello World Sam!")
 }
 fun main(args: Array) {
     printHi()
 }
 helloworldsam.printHi()

The Best Way to Collect a Flow in Kotlin – launchIn

At some point you’ll need to collect (receive items) from a Flow (reactive stream) within a Kotlin Coroutine.  More than likely you will use a launch on a CoroutineScope, and then collect like this:

scope.launch {
  flow
    .onEach { println(it) }
    .collect()
}

This works great, but there is a better way for most use cases. It’s using a function called launchIn. What’s launchIn? It’s just shorthand to do what you did above. This is the equivalent logic as above, but using launchIn.

flow
  .onEach { println(it) }
  .launchIn(scope)

This is less code to write, but more importantly it’ll get you out of some hard to debug situations when collecting from Flows. The non obvious thing to understand is that collect() will block the coroutine until the flow has finished emitting. This behavior is sometimes desired, but for me it’s not in most cases.

In the example below, you’d think that both Flows are being collected at the same time, but flow1 is collected until the Flow finishes emitting, and then flow2 is collected until it is finished emitting.

scope.launch {
  flow1
    .onEach { println(it) }
    .collect()
  
  // Will not run until flow1 finishes emitting
  flow2
    .onEach { println(it) }
    .collect()
}

To collect both in parallel, you’d need to write this:

scope.launch {
  flow1
    .collect { println(it) }
}
scope.launch {
  flow2
    .collect { println(it) }
}

This is where launchIn comes to the rescue to make this reach much easier in my opinion. Here is the equivalent using launchIn:

myFlow1
  .onEach { println(it) }
  .launchIn(coroutineScope)
myFlow2
  .onEach { println(it) }
  .launchIn(coroutineScope)

I like launchIn because it’s less code to write, I don’t have to have indentation, and I just found it easier to understand.

In no way does this mean that the normal launch() and collect() aren’t great things to use, but for most use cases, I’d suggest considering using launchIn().

My MutableStateFlow Didn’t Emit!

I am in love with MutableStateFlow which is part of the Kotlin Coroutines Library. It makes managing state, and exposing it as a reactive Flow so easy.

You just create a MutableStateFlow(initialValue), and then you can later on just set myMutableStateFlow.value = newValue, and if it has changed, it’ll emit a new event as a reactive event on the Flow!

This is amazing, but in order to make this work the way you want it to, you HAVE to use Immutable Data Structures.

Why? Glad you asked, here we go.

How MutableStateFlow’s “value” Property Works

Just set mutableStateFlow.value=newValue and the value will be emitted! This Flow is a great way to keep track of state where you only want to emit when there is a change.

You can set your mutableStateFlow.value many times, but unless !oldValue.equals(newValue), then a new value won’t be emitted. That way subscribers don’t need to use distinctUntilChanged() to filter out duplicates.

Scenario where “My MutableStateFlow Didn’t Emit!

I’ve simplified a scenario I ran into to share how MutableStateFlow may not work the way you think you should. I banged my head on the wall trying to figure out what was happening, and hopefully this simplified example will save you a headache.

/** Class with Mutable Data */
data class SomePojo(var name: String = "placeholder")
val somePojo = SomePojo()
val mutableStateFlow = MutableStateFlow(somePojo)
println("INITIAL: ${mutableStateFlow.value}")

// Update the value
somePojo.name = "Something Different"

// Print MutableStateFlow current value, but realize the value has changed because the data in that object has been mutated
println("CURRENT: ${mutableStateFlow.value}")

// Try to assign the new value to the MutableStateFlow, but they are already equal!  This means no emissions will occur.
mutableStateFlow.value = somePojo

// The result is the same, and no value was emitted.
println("UPDATED: ${mutableStateFlow.value}")

The output will be:

INITIAL: SomePojo(name=placeholder)
CURRENT: SomePojo(name=Something Different)
UPDATED: SomePojo(name=Something Different)

How I Went Wrong

  • My state model was “SomePojo” which had a mutable var “name” on it.
  • The “somePojo” variable was initially set as mutableStateFlow.value
  • I set myPojo.name=”Something Different” to change the name.
  • I wanted to emit the new state, so set mutableStateFlow.value=somePojo.
  • My MutableStateFlow Didn’t Emit!
  • 🤔
  • The current value of mutableStateFlow.value was already set to myPojo, and I had modified the property “name” on it.
  • As seen above, MutableStateFlow.value will emit unless the oldValue.equals(newValue). Because I had modified the mutable property name, when the equals() comparison happened.
  • The value was already the same, and therefore no change occurred, and there was no emission.
  • Whoa… that’s confusing. Yes, but it makes sense now.

Conclusion

There are things you can do to avoid running into this problem. Use immutable data structures (classes with all val properties) with MutableStateFlow to avoid unexpected behavior.

If you mutate (change) the value of an object that’s currently the state of a mutable state flow, it won’t emit a new value when re-assigned because it only emits when the value has changed. In this case the mutable value has changed… but so has the underlying state, so when compared it looks as if nothing has changed.

Try for yourself with this Unit Test Gist: https://gist.github.com/handstandsam/1007031cea66e9862bed44840fafb92e

Wrapping Mockito Mocks for Reusability

My general advice about Mockito is to try and avoid it when you can. Ryan Harter has an awesome companion post called Replacing Mocks which shows some potential pitfalls of using Mockito Mocks, as well as how to avoid using it by restructuring code. However, sometimes Mockito can be the right tool to use for mocking dependencies in a unit test when a code refactor isn’t possible due to constraints. I use Mockito-Kotlin which helps leverage Mockito in Kotlin code.

In this post I show how you can wrap a Mockito mock to avoid using verbose syntax (“whenever”, “verify”, etc.) all over the place. I call this wrapper around the Mockito mock a “Fake”.

// Fake Wrapper around Mockito Mock (See implementation below)
val fakeOven = FakeOven()

// Access and Use Mockito Mock
val oven : Oven = fakeOven.mock

// Clean API to Setup Mocks (using Wrapper)
fakeOven.givenOvenResult(OvenResult.Success)

// Clean API to Verify Mocks (using Wrapper)
fakeOven.thenOvenSetTo(temperatureFahrenheit = 350, timeMinutes = 30)

Benefits:

  • Single Responsibility – Mocking logic out of your test class.
  • Cleaner Tests – Avoid using “when”, “whenever” and “verify” all over the place.
  • Less Duplication – Can be reused across tests and for future tests you may write.

Example: Baking with an Oven

In this example we bake a Cake which requires an Oven. We need to mock the Oven.

NOTE: We could try various approaches for this example, but for the purpose of explaining this strategy, we’ll use the “Fake” Mockito wrapper.

/** Class that uses [Oven] */
class Dessert(val oven: Oven) {
    fun bakeCake(): OvenResult {
        oven.setTemperatureFahrenheit(350)
        oven.setTimeMinutes(30)
        return oven.start()
    }
}
/** Class we will use Mockito to Mock */
class Oven {
    fun setTemperatureFahrenheit(tempF: Int) {
        // ...
    }

    fun setTimeMinutes(minutes: Int) {
        // ...
    }

    fun start(): OvenResult {
        // ...
    }
}
/** Whether the Oven command was successful, or something happened */
sealed class OvenResult {
    object Success : OvenResult()
    data class Failure(val e: Exception) : OvenResult()
}

Original Test 🤷🏽‍♂️

import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import org.junit.Test

/** Test Dessert Baking */
class DessertTest {

    @Test
    fun bakeCakeSuccess() {
        val oven: Oven = mock()
        val dessert = Dessert(oven)

        // Setup
        whenever(oven.start()).thenReturn(OvenResult.Success)

        // Execute Code
        dessert.bakeCake()

        // Verification
        verify(oven).setTemperatureFahrenheit(350)
        verify(oven).setTimeMinutes(30)
    }
}

FakeOven – Mockito Mock Wrapper 🤔

/** Wraps the Mockito mock for reuse */
class FakeOven {

    val mock: Oven = mock()

    fun givenOvenResult(ovenResult: OvenResult) {
        // Setup
        whenever(mock.start()).thenReturn(ovenResult)
    }

    fun thenOvenSetTo(temperatureFahrenheit: Int, timeMinutes: Int) {
        // Verification
        verify(mock).setTemperatureFahrenheit(temperatureFahrenheit)
        verify(mock).setTimeMinutes(timeMinutes)
    }
}

Updated Test – Using Fake Mockito Wrapper ✅

class DessertTestWithFake {

    @Test
    fun bakeCakeSuccess() {
        val fakeOven = FakeOven()
        val dessert = Dessert(fakeOven.mock)

        fakeOven.givenOvenResult(OvenResult.Success)
        dessert.bakeCake()
        fakeOven.thenOvenSetTo(
            temperatureFahrenheit = 350,
            timeMinutes = 30
        )
    }
}

We use “fakeOven.mock” to fulfill the “Oven” dependency, and control the behavior using the wrapper we have created.

val fakeOven = FakeOven()
val oven : Oven = fakeOven.mock

Conclusion

Isolate usage of Mockito, and avoid scattering it all over your tests. By using this pattern of wrapping a Mockito Mock in a fake, your tests that require Mockito are a little bit better.

SQLDelight 1.x Quick Start Guide for Android

SQLDelight is most well known as a Kotlin multiplatform database library. As an Android Developer, the most compelling reasons to use SQLDelight are:

  • Kotlin first
  • SQL first
  • Typesafe generated code
  • Unit tests don’t require an Android device

Documentation for how to use SQLDelight on just Android (without Kotlin mutliplatform) is lacking, so I wanted to create this guide to get people going fast on just Android.  This example is here to get you started, but does not reflect best coding practices.  I did this in order to make this quick start guide concise, and approachable.

1) Add Gradle Plugin to the buildscript classpath

In your project’s root build.gradle file, add the following dependency under buildscript dependencies.

classpath "com.squareup.sqldelight:gradle-plugin:1.3.0"

2) Apply the Gradle Plugin to your App or Module

You need to add the SQLDelight Gradle Plugin to your app or module because SQLDelight uses code generation from SQL files (*.sq), instead of code generation based on annotations (kapt).  This gives us incremental builds, but requires us to add a plugin to our root build.gradle file.  By doing code generation from SQL files, SQLDelight can generate code that works on any platform, because at the end of the day you are just executing SQL statements.

apply plugin: "com.squareup.sqldelight"

3) Add/Write the SQL (.SQ) File

You need to create a source folder for your SQL (*.sq) files at “src/main/sqldelight” folder, next to “java” or “kotlin” source folder. In this example, we’ll be representing a “item” in a shopping cart (Example from Shopping App) and the ItemInCartEntity.sq file in the following location:

MODULE_OR_APP/src/main/sqldelight/com/handstandsam/sqldelightquickstart/ItemInCartEntity.sq

ItemInCartEntity.sq

CREATE TABLE itemInCart (
    label TEXT NOT NULL UNIQUE PRIMARY KEY,
    image TEXT NOT NULL,
    quantity INTEGER NOT NULL DEFAULT 0,
    link TEXT
);

selectAll:
SELECT *
FROM itemInCart
ORDER BY label;

insertOrReplace:
INSERT OR REPLACE INTO itemInCart(
  label,
  image,
  quantity,
  link
)
VALUES (?, ?, ?, ?);

selectByLabel:
SELECT *
FROM itemInCart
WHERE label = ?;

empty:
DELETE FROM itemInCart;

deleteByLabel:
DELETE
FROM itemInCart
WHERE label = ?;

4) Install the SQLDelight Android Studio Plugin

The Android Studio Plugin is not required, but is super helpful for syntax highlighting, code completion, warnings and navigation.

.sq file without the SQLDelight Android Studio Plugin

Adding the SQLDelight Android Studio Plugin

A .sq file after the SQLDelight Android Studio Plugin is Installed

5) Add the Dependency Used for in Memory Unit Tests

You need the JdbcSqliteDriver for Unit Tests. This allows you to run your database tests without Android device. This means blazing fast tests without the hassle of connecting a device!

Add the following Unit Test dependency:

testImplementation "com.squareup.sqldelight:sqlite-driver:1.3.0"

Note: You don’t have to write unit tests and could technically skip this and the next step, but do yourself the favor and write tests from the beginning.

6) Write Unit Tests

Now that everything is set up, you should write a unit test that runs on your computer to make sure it’s all working.  This is a huge benefit over the Room library that comes with Android Jetpack because we can completely decouple ourselves from knowing what Android is.  This allows us to verify our setup and to have a quick feedback loop to ensure everything is working.

Use the JdbcSqliteDriver for an in memory version of your database for unit tests to avoid state between tests. However, be sure to also call Database.Schema.create(sqlDriver) or your in memory tests will not work. In order to make sure I call it, I use an “apply” to make sure I do it along with creating an instance of the driver.

private val inMemorySqlDriver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY).apply {
    Database.Schema.create(this)
}

Here is the full ItemDatabaseTest.kt file containing the Unit Test.

package com.handstandsam.sqldelightquickstart

import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver
import org.junit.Assert.assertEquals
import org.junit.Test

class ItemDatabaseTest {

    private val inMemorySqlDriver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY).apply {
        Database.Schema.create(this)
    }

    private val queries = Database(inMemorySqlDriver).itemInCartEntityQueries

    @Test
    fun smokeTest() {
        val emptyItems: List = queries.selectAll().executeAsList()
        assertEquals(emptyItems.size, 0)

        queries.insertOrReplace(
            label = "Pineapple",
            image = "https://localhost/pineapple.png",
            quantity = 5,
            link = null
        )

        val items: List = queries.selectAll().executeAsList()
        assertEquals(items.size, 1)

        val pineappleItem = queries.selectByLabel("Pineapple").executeAsOneOrNull()
        assertEquals(pineappleItem?.image, "https://localhost/pineapple.png")
        assertEquals(pineappleItem?.quantity?.toInt(), 5)
    }
}

7) Add the SQLDelight Android Driver Dependency

Now that we have the plugin working and unit tests passing, we need to integrate with our Android app. Add this “implementation” AndroidSqliteDriver dependency on SQLDelight.

implementation "com.squareup.sqldelight:android-driver:1.3.0"

If you only have an “app” module, then add it to that, but if you are in a multi-module project, I would highly suggest creating a “db” module (or similar) for this code.

8) Write your SQLDelight Android Code

In order to just show this working on Android, I pasted the code into the MainActivity, which is not what you should do, but it helps you validate that it’s actually working on Android.

You will need to use the AndroidSqliteDriver in order for SQLDelight to correctly write to the Android database. The JdbcSqliteDriver was helpful for allowing us to do in-memory unit testing, but it only keeps the database in memory, and would never save between app launches.

val androidSqlDriver = AndroidSqliteDriver(
    schema = Database.Schema,
    context = applicationContext,
    name = "items.db"
)

val queries = Database(androidSqlDriver).itemInCartEntityQueries

val itemsBefore: List = queries.selectAll().executeAsList()
Log.d("ItemDatabase", "Items Before: $itemsBefore")

for (i in 1..3) {
    queries.insertOrReplace(
        label = "Item $i",
        image = "https://localhost/item$i.png",
        quantity = i.toLong(),
        link = null
    )
}

val itemsAfter: List = queries.selectAll().executeAsList()
Log.d("ItemDatabase", "Items After: $itemsAfter")

9) Run The Code on Android

Hit the run button and filter Logcat so you can see that you have successfully added and retrieved data from SqlDelight on Android!

10) Peek at the “Magically” Generated Code

It’s cool to see where the plugin puts the code it generates in “build/sqldelight” and it may help you understand how SQLDelight works. The generated code is super easy to read since it uses Kotlin Data Classes, and the SQL code is just taken almost directly from your .sq file that you already wrote, but wrapped in a type-safe way.

Conclusion

These steps are all you need to get started with SQLDelight 1.x on Android.  Here is a pull request that contains all the changes mentioned in this post: https://github.com/handstandsam/SQLDelightQuickStart/pull/1/files.  This article was written when version 1.1.4 was released, but has been updated to version 1.3.0.  Check out the SQLDelight change-log to see the latest released version.

Enjoy the beautiful generated Kotlin code which is generated from our .sq files, and enjoy validating your code via unit tests that can run without an Android device!

Related Links:

“It Depends” Is The Answer To Your Android Question

Android Questions:

  • Should I use an Actor or StateFlow? “It Depends”
  • Should I use Mockito? “It Depends”
  • Should I put my Kotlin code in src/main/java? “It Depends”
  • Should I use Flutter? “It Depends”
  • Should I wrap a 3rd party library’s API? “It Depends”
  • Should I install the Android Q Beta? “It Depends”
  • Should I use Dagger? “It Depends”
  • Should I use Kotlin or Java? “It Depends”
  • Should I use React Native? “It Depends”
  • Should I use Dependency Injection? “It Depends”
  • Should I use OkHttp? “It Depends”
  • Should I use Multiple Activities or a Single Activity? “It Depends”
  • Should I upgrade to the latest Support Library? “It Depends”
  • Should I use a library that’s in Alpha? “It Depends”
  • Should I use Room or SqlDelight? “It Depends”
  • Should I write Espresso tests? “It Depends”
  • Should I bump my minSdk to 28? “It Depends”
  • Should I use MVP or MVVM or MVI or MVC? “It Depends”
  • Should I use RxJava or LiveData? “It Depends”
  • Should I learn Android or iOS? “It Depends”
  • Should I start a blog? “It Depends”
  • Should I start a side project? “It Depends”
  • … Insert Your Question Here  … “It Depends”

“It Depends” Is Technically Correct 💯% of the Time

While “It Depends” is technically correct since there is no absolute answer in software, it still doesn’t make it a good answer.  “Probably Should” is an answer well, but when you think about it, it’s still a variation of “It Depends”.

“It Depends” Is a Crappy Answer

Juhani brings up a great point about responsibility in his tweet above.  Opinions are great because they are shaped by experience.  As you gain experience, it’s important to share your opinions and discoveries because they will help provide insight and context into a topic.  That empowers the person searching for the answer to make a decision, because you can’t write software with “It Depends”, since it won’t compile. 😂

There Is No Perfect Answer

My goal of this post was to point out that there are tons of ways to do things, but based on your team, use case and target audience, there is no good 100% right answer for any topic.  I can strongly urge you to use Kotlin, but if you are building something that people are willing to pay millions of dollars from and they need Java, then Java is your right answer.  The important part is to keep listening to opinions and discoveries that are brought up, but know they are not a one-size-fits-all solution.

I’m happy to share my opinion and experiences with you on any topic if you reach out to me on Twitter @HandstandSam, and I’ll do my best not to answer with “It Depends”. 🙂

When You Should Use Null in Kotlin

I was recently reviewing code with a developer that is learning Kotlin and they were adamant that:

“You should never use null. Null is BAD.” 


Null has got a bad rap.  Yes, it’s to be avoided in most code, but in Kotlin, null is part of the type system and the compiler will tell you when you haven’t handled it appropriately.  However, just because it’s easy to identify null in Kotlin doesn’t mean we should use it everywhere.  There are perfectly appropriate safe uses of null in Kotlin.

Valid usages of null in Kotlin

1) When Representing “No Value”

 var user: User? = null

If you need a way to represent whether a value is initialized or whether it has no value, then null is appropriate in this case.

2) Consuming External Data Sources

If you need a way to represent whether a value is available or unavailable, then null is appropriate in this case as well.

Apps must consume external data sources that we don’t have full control over.  When operating in a non-hermetic environment, we run into cases where some data sources are not reliable and may provide null content.  Whether it is a REST API, or an Android System Service, representing data that may not be available as a nullable type is important because it allows us to create code paths to handle both cases.

Functional Purity

While it seems ideal to never have null in your code, it’s not always possible to avoid, and sometimes shouldn’t be avoided.

Using a Sealed Class to Avoid Using Null

sealed class UserWrapper {
  object UninitializedUser : UserWrapper()
  data class InitializedUser(val user: User): UserWrapper()
}

Creating a sealed class for the sole purpose of avoiding the use of null introduces unnecessary boilerplate code.

Don’t get me wrong, sealed classes are one of my favorite features of Kotlin though, and are great if you need to handle more scenarios than just initialized or uninitialized, but if that’s all you need, then using null is a valid approach.

Using a Nullable Field

val user : User?

Using a nullable field is much more concise in your definition than a sealed class.  It’s also more concise to use a ?. operator instead of an “if” or “when” statement to process your sealed class.

Offensive Programming

If you really want to, you can run around with a bunch of !! (double bangs) in your code.  However, you are living dangerously and your code will crash if the value is null.  This may be what you are looking for though if you are trying to follow practices of Offensive Programming.

//This will crash if the user is null
handleNonNullUser(user!!)

Defensive Programming has us cover all possible code paths just in case it might occur.  When you do have these extra code paths, provide some fallback logic or warning logs.

Ways of Handling Null

If you check once that the value is non-null, the Kotlin compiler is smart enough for you to write your code without providing any !!.  Then process the non-null val after it’s been checked once, to avoid multiple checks throughout my code. Here are some ways to do this:

Null Comparison 😐
if (user != null) {
  handleNonNullUser(user)
} else {
  Log.w(TAG, "user was null")
  handleNullUser(user)
}

This is very Java-esque, but it does the trick.

Let 🙂
user?.let {
  //Work with non-null user
  handleNonNullUser(user)
}

I like using let because it allows us to write much more idiomatic Kotlin.  It does not allow us to handle null, “else” case though.

Early Exit 👍
fun handleUser(user : User?) {
  user ?: return //exit the function if user is null
  //Now the compiler knows user is non-null
}

If the value is null, you can exit the method immediately, otherwise you can operate on the non-null value (because the Kotlin compiler is that smart).

Immutable Shadows 👥😲

Using immutable “variable shadows” a really cool way of providing null safety.  It will do an “Early Exit” if the value is null, otherwise it will assign the non-null value of the var to an immutable val allowing the compiler to know the value is non-null.  Thanks to Gabriel Peal for this tip!

var user : User? = null

fun handleUser() {
  val user = user ?: return //Return if null, otherwise create immutable shadow
  //Work with a local, non-null variable named user
}

Conclusion

There is no doubt that you will write better code if you try and stick with non-null and vals where you can. However, if you run into a scenario where using null is the right thing to do, then don’t fight it.

Code appropriately using one of the techniques I’ve shared in this article to handle null effectively.  These techniques allow you to avoid having to use the !! operator which can lead to NullPointerExceptions.  The whole point of having a nullable type in Kotlin is to allow you to avoid NullPointerExceptions at compile time, instead of them sneaking up on your at run time.

You may be thinking that this post is similar to Roman Elizarov’s post “Null is your friend, not a mistake“, and you would be right.  His post inspired me to finally publish this, and you can read more of the story behind publishing this post here.