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

Kotlin Actors – No Drama Concurrency

Kotlin Actors are part of the Kotlin Coroutines library. I’ll walk you through the reasons why I use Kotlin Actors to achieve concurrency, while leveraging Coroutines to process reactive events in unknown order.

Concurrency?

  • Allows events to happen out-of-order or in partial order, without affecting the final outcome.
  • This allows for leveraging parallel execution without giving up determinism.

Why Does Android need Reactive Programming?

  • Click Events
  • Intents
  • Networking Requests
  • Disk Writes
  • etc.

Kotlin Coroutines?

Essentially, Kotlin Coroutines are light-weight threads. They are launched in a context of some CoroutineScope.

Kotlin Actors?

  • A Single Kotlin Coroutine
  • Processes incoming Messages
  • Backed by a Channel
  • Concurrent

Actors receive Messages (Intentions) via a Channel

Channels are the only way to safely communicate across Coroutines.

This example implements a Shopping Cart Dao from my GitHub Project ShoppingApp. I’ve created a type called Intention which are sent across the channel as messages. The intentions represent actions I want to perform, but keep my data thread safe.

sealed class Intention {
    class FindByLabel(
        val label: String,
        val deferred: CompletableDeferred<ItemWithQuantity?>
    ) : Intention()

    class Upsert(val itemWithQuantity: ItemWithQuantity) : Intention()

    class Remove(val itemWithQuantity: ItemWithQuantity) : Intention()

    object Empty : Intention()
}

Actors Process Messages Sequentially in a for() loop

These messages (Intentions) come in across a Channel from other Coroutines, get queued, and then get processed by the Actor sequentially to achieve concurrency.

scope.actor<Intention> {
    for (intention in channel) {
        // Process Messages/Intentions
        when (intention) {
            is Intention.FindByLabel -> {
                // ...
            }
            is Intention.Upsert -> {
                // ...
            }
            is Intention.Remove -> {
                // ...
            }
            is Intention.Empty -> {
                // ...
            }
        }
    }
}

Sending Messages to the Actor – send() vs offer()

To send messages to the actor, you send a message to it using actor.send(intention) or actor.offer(intention). Here are the differences between them (from the Kotlin documentation of SendChannel).

CompletableDeferred to await() Results

We send in messages to the actor, but sometimes we want to wait for a result once the message has been processed by the actor. We use CompletableDeferred to do this. We await() the result, like in this example where we are querying for a value:

class FindByLabel(
    val label: String,
    val deferred: CompletableDeferred<ItemWithQuantity?>
) : Intention()
// ---
val deferred = CompletableDeferred<ItemWithQuantity?>()
actor.send(
    Intention.FindByLabel(
        label = label,
        deferred = deferred
    )
)
val result : ItemWithQuantity? = deferred.await()

Aren’t Actors Marked with @ObsoleteCoroutineApi?

Yes, but complex actors will also support the same use cases, and there will be a clear path. Also, there is no planned replacement at this point. See the response from the Kotlin Coroutines tech lead from the GitHub issue:

Video & Slides

I was able to present this to Boston Android meetup group and 18 other meetup groups on Tuesday, July 14th which was an amazing experience. The video will be available soon and I’ll be sure to put it here. Here are the slides for now.

Links

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.

Unlocking Biometric Prompt – Fingerprint & Face Unlock

AndroidX Biometric gives us a single API for supporting Biometrics on Android devices via the BiometricPrompt, and a fallback Fingerprint dialog for API 23-27.  This post does a side-by-side comparison of what it looks like on the Pixel 4, Pixel 3, and an API 26 Emulator to show you what it looks like on different devices, hardware and different builder configurations.

Currently, the Pixel 4 is the only device that supports Face Unlock via the Biometric Prompt. There aren’t even emulators that support it.  I ended up with a Pixel 4, and wanted to create this post with you to save you some 💰 and⌚.

What is the Android Biometric Prompt?

The Android Biometric Prompt was released as part of the Android OS in API 28 (Pie) to replace the FingerprintManager.  Its goal is to make a standard way of interacting with Biometrics via the operating system, and also support multiple types of biometric types such as Fingerprint, Face & TBD.

One of the downsides of the Biometric Prompt is that we are asked to use the terminology “Biometric” instead of “Fingerprint” or “Face” because the BiometricManager doesn’t tell us the type of biometric the user will use.  Read more about how to provide better user experiences through tailored biometric messaging in my previous post.

Using AndroidX Biometric

I’m going to show you various configurations of the library in this post, but refer the documentation from Google for more information.

BiometricPrompt.PromptInfo.Builder()
 .setTitle("Authenticate with Face")
 .setNegativeButtonText("Cancel") 
 .setConfirmationRequired(true)
 .build()
Face Unlock Success

Face Unlock Fail & Retry

Confirmation Required

There is a configuration parameter for Biometric Prompt Info Builder called “setConfirmationRequired” which, when set to false, passively authenticates the user without any interaction (other than looking at the phone). Note: This only works for Face Unlock since a Fingerprint is confirmation.

.setConfirmationRequired(false)

.setConfirmationRequired(true)

NOTE: There is a setting which allows a user to ALWAYS confirm when using Face Unlock, so be aware of that when building your applications.

Conclusion

Use AndroidX Biometric.  It feels like they were still working out the kinks when they release Biometric Prompt in API 28.  The AndroidX Library has some device specific workarounds and the fallback dialog for API 23-27.  It’s weird that there isn’t better documentation about the user experience for each biometric type, which is why I wrote this post. Hopefully you won’t have to spend hundreds of dollars on a Pixel 4 now, and have a better idea of what the Biometric Prompt looks like under different configurations.

Links:

Companion Video on AsyncAndroid

Android Device Mirroring and Recording

Mirroring and Recording what is on your physical Android Device to your computer isn’t trivial, but is an important skill to have as a developer. Being able to share and record what you see on your Android screen is super helpful for live demos, your GitHub PRs, and blog posts. There are a lot of ways to do this, but there is no perfect way of doing it.  I’ll walk you through my flow in this post.

I used Vysor for a long time, and think it’s a great tool to use to mirror your device onto your computer.  Vysor is really easy to use, but the free version does have feature limitations such as mirroring quality, and advertisements every 15 minutes. When I tried scrcpy with my development setup, I found that for me it worked the best out of any of the other tools to do this.

See the scrcpy GitHub page for all the instructions, but if you are on a Mac, already have “adb” configured, and have homebrew, then just run brew install scrcpy.  After it’s installed you can just type “scrcpy” into your terminal and it’ll launch device mirroring.

Now that you have your Android device mirroring on your computer, you need to record it.  There are a lot of tools that allow you to screen record into a GIF, but I use Kap.  You can launch Kap via the menu bar with this icon:

Then select an area of your screen manually,

… or choose an open application window.

After you are done recording, stop the recording with the button in the menu bar.

Trim your content, select resolution, frames per second (FPS), export format (GIF, MP4), and export!

Drag and drop the file into your GitHub PR.  NOTE: 10MB is the limit for image attachments in a PR, so adjust your frames per second and image resolution to find the right size.

Your PR has a GIF of your device! 🎉 This makes your PR a lot easier to understand to a reviewer, and anyone looking back at this PR in the future.

Conclusion

Adding GIFs to PRs and blog posts really keep readers engaged.  It especially did if you are actually reading this far 😎.  I hope my you find my recommendations something that help your Android development and content creation!

Links:

Companion Video on AsyncAndroid

Where Should I Put Kotlin Code in an Android Project?

Based on the results of a poll of 371 Android developers, the majority of responses endorse using “src/main/java” for Kotlin code in Android only projects.  My answer is always it depends, but let’s see why you would use “src/main/java” the majority of the time, and why you’d want to use “src/main/kotlin” in some cases.

Why src/main/java?

79% of people say it should go in “src/main/java”.  Here’s why:

  • The official documentation Android Developer Documentation recommends it, and shows additional configuration you must add to support a “src/main/kotlin” source set.
    android {
       sourceSets {
           main.java.srcDirs += 'src/main/kotlin'
       }
    }
    
  • When you create a new project Kotlin project in Android Studio, it uses “src/main/java” as the location for your Kotlin code.
  • I personally really like it for the reason that all of your source is in a single place, making it easier to find related code in a project mixed with Java.  This can help maintain consistency to have all your code in one location for any project that has or had Java code in the past.
  • There are no longer issues with the Kotlin Gradle Plugin which prevented you from mixing Kotlin and Java code in the same directory.

Why src/main/kotlin?

21% of people say it should go in  “src/main/kotlin”.  Here’s why:

  • Some projects like the clean separation of the languages into different directories.  While this is nice idea, from a pragmatic standpoint, having all your code in a single place is the ideal for discovery and browsing in my opinion.
  • Some projects are 100% Kotlin, so having this directory structure clearly calls this is a single to only write code in Kotlin.  This comes with the configuration overhead of each module, but if you share Gradle config between multiple projects, you’d only have to do this once.
  • OkHttp does it, but realize this is because it’s a Kotlin only project that is meant to be used on multiple platforms (including the JVM & Android).

Kotlin Source Sets beyond Android

The answers above only apply for Android only projects.  Kotlin is more than just a programming language used on Android.  It’s being used for Kotlin Multiplatform to create code that runs on iOS, it’s used in Kotlin Native to get blazing fast, low level speed and Kotlin JS to run in your Node server or web application.

You can see how some Kotlin Multiplatform projects are using source source sets such as “commonMain” where common code used for all platforms is kept, and then you can have additional source sets like “iosMain” for platform specific implementations.

I’m not going to pretend that I’m a Kotlin Multiplatform expert, but check out the official documentation, and the KaMPKit Github Repo from TouchLab to see a sample project of Kotlin Multiplatform.

Android Biometrics UX Guide – User Messaging

Users Say: “Biometric…🤷‍♂️🤷‍♀️?”

When I’ve demoed “Biometric” UIs to non-developers, many say:

Why don’t you just say “Fingerprint” or “Face Unlock”?

The reason is that the Biometric APIs have no way to find the type of biometric that will be used.  That’s why we are stuck with using “Biometric” as a catch all.  You can see the terminology being used in Google’s Android Developer Training on Biometric Auth.

We’re also working with the UX / design team on clear iconography/messaging – in the meantime, our suggestion to developers has been to use something along the lines of “Biometric settings”, or “Use biometric”, etc.
– Googler’s Response on Issue Tracker

I have read “BiometricManager” and “BiometricPrompt” enough times to get used to it, but users haven’t.  So let’s see what we can do to create better user messaging.

Option A: Describe “What” Instead of “How”

Say what the user is going to do like “Unlock” or “Login” or “Confirm” or whatever.  Just don’t mention how the user will do it via a biometric.  The system will show the UX for the biometric type anyways in the Biometric Prompt, but your customized wording will be whatever you provide.

 

Consider these scenarios as well:

  • What will you call this on your settings page?
  • What iconography will you use for “Biometric” on a Pixel 4 with Face Unlock? A Fingerprint?  That’s not ideal.
  • How will you encourage your users to use biometrics in your app?  Maybe you could say “Unlock Faster Next Time” and it can be implied that a biometric will be used?

You might be able to get away with this messaging, and if you can, congrats! 🎉

Option B: Write Code and Cross Your Fingers🤞

It’s possible to figure out what biometric will be used the majority of the time, and I’ll show you how. 😎

Running on Pixel 3 Running on Pixel 4

Step 1) See If Device Has Biometric Support

Ask the BiometricManager if it canAuthenticate(), and if it’s successful, or says the user has not enrolled their biometrics, then you know the device is capable.

val biometricManager = BiometricManager.from(context)
val isCapable = when (biometricManager.canAuthenticate()) {
    BiometricManager.BIOMETRIC_SUCCESS,
    BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> true
    else -> false
}

The result of this just tells us that the device is capable of using the BiometricPrompt.

Step 2) Ask PackageManager For Available Features

There are currently only 3 types of Biometrics as of API 29 (Android 10).  The Android PackageManager can be queried to see if these features are available on the device.

// Get Package Manager
val packageManager : PackageManager = context.packageManager

Based on these, you should know, but there are edge cases:

  • One is available – If only one is available and the rest are not, you should feel pretty confident that you know the exact type of biometric that will be used.
  • More than one available – It is possible that a device could have more than one biometric feature.  It could have Face Unlock and Fingerprint.  Android is an open platform, and Google has said that OEMs could do this if they choose.
  • None are available –  If this is the case, and the BiometricManager said you canAuthenticate(), then some sort of biometric is available that we have never seen before.  This could be the case if this code is run on a future version of Android with a Biometric type we don’t know about.

Step 3) Determine Supported Biometrics

Based on all the logic above, we will end up with one of the following results.

sealed class Biometrics {
    object None : Biometrics()
    sealed class Available : Biometrics() {
        object Face : Available()
        object Fingerprint : Available()
        object Iris : Available()
        object Multiple : Available()
        object Unknown : Available()
    }
}

You can then use a “when” statement to create user messaging for a specific biometric hardware type. 🎉

What are future Biometric Types?

We don’t know yet.

Biometric APIs are meant to be more future-looking. We “could” expose something like authenticate(type, info, crypto) etc, but it exposes more API surface and thus has the chance of causing more fragmentation across OEMs.
– Googler’s Response on Issue Tracker

In order to be more open ended, these Biometric APIs are built in a way where generic messaging is the recommended approach currently.

Conflicts with Material Design Guidelines

The Material Design guide for Fingerprint explicitly says to maintain consistency with Android Settings. Such as “Confirm fingerprint”.  The BiometricManager tells us if a user “canAuthenticate()“, but doesn’t tell us what types of Biometrics are available on the device or which one (if more than one) is currently enabled.  The rationale for this:

If new sensors are developed, we would need to keep updating the “type” list, and apps would also need to keep updating to use the new types. Perhaps there’s a way to make that work, just we haven’t spent much time investigating.
– Googler’s Response on Issue Tracker

I think this is a great way to do it, and aligns with user expectations, but this is not available with current Biometric APIs. 😞

Pixel 3 Settings Pixel 4 Settings

Conclusion

This all sounds like a lot of work.  You can just use “Biometric” and you’ll probably be fine.  Users will get used to it eventually, right?  No matter how hard we try at this point, we will end up having to use that terminology in the cases where “Multiple” or “Unknown” biometric features are available anyways.

It kinda stinks that we got forced to use these APIs since FingerprintManager is deprecated, and the Biometric APIs have a lot of these little workarounds you may need to do.  I understand the rationale behind it from an OS standpoint, but I hope that Google exposes the type(s) of Biometric available on the device to use.  That way we are sure, and aren’t doing all this work to try and figure it out.

Recommendations

  1. Must: Use the AndroidX Library.  It’s a wrapper on top of the Android OS APIs and deals with specific workarounds, as well as provides a FingerprintManager fallback for devices prior to API 28 which don’t have a BiometricPrompt in the OS.
  2. Recommended: Checkout Biometricks which is a library in development to do what is mentioned in this article.  It has a sample app and more.
  3. Recommended: Do some user testing.  I’m giving some advice from what I’ve seen, but you may find something different with your users.  Your users are your source of truth.

Disclaimers

  • This is a UX guide, and not anything related to security of using Biometric features of Android.
  • These are my personal observations and opinions.

Related Links

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:

Sharing Gradle Configuration in Multi-Module Android Projects

Using multiple modules in our Android projects help us split apart our code into logical components.  They also can enable faster incremental builds, and more modular code.  One problem with multi-module projects is that there is a lot of verboseness of configuration.  This post shows you a method of sharing common configuration between your Android Library modules in order to cut down on boilerplate Gradle configuration.

I made this change in a PR in my ShoppingApp project on GitHub and ended up deleting a net 90 lines of code over 7 library modules.

apply from: “____.gradle”

You can add the contents of another Gradle file into your current one by using “apply from: ” and specifying the file whose content you want to add.

apply from: "$rootProject.projectDir/android-library.gradle"

$rootProject.projectDir

Modules can exist in different directory structures, so by leveraging the $rootProject.projectDir property, we specify paths based on the root project directory.

Original Library Module build.gradle

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
    compileSdkVersion Versions.compile_sdk

    defaultConfig {
        minSdkVersion Versions.min_sdk
        targetSdkVersion Versions.target_sdk
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
}

dependencies {
    implementation project(Modules.models)
    implementation Libs.kotlin_std_lib
}

Resulting Library Module build.gradle

apply from: "$rootProject.projectDir/android-library.gradle"

dependencies {
    implementation project(Modules.models)
    implementation Libs.kotlin_std_lib
}

Shared Gradle File

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
    compileSdkVersion Versions.compile_sdk

    defaultConfig {
        minSdkVersion Versions.min_sdk
        targetSdkVersion Versions.target_sdk
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
}

If You Do Something 3+ Times, Extract Out Functionality

Whenever it’s possible and makes sense, use common configuration to reduce boilerplate.  This same rule applies if you are writing code, or writing Android Gradle configuration.  This post shares an “easy win”, that you may be able to use to help better manage your multi-module project. There is so much more you can do to clean up your builds by leveraging buildSrc where you can write in Kotlin, Java or Groovy, but that’s for another post.

“It Depends” Is The Answer To Your Android Question

Android Questions:

  • Should I use Kotlin Multiplatform? “It Depends”
  • Should I use Kotlin Multiplatform for UI? “It Depends”
  • 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”. 🙂