Add Room and then Room KMP to Android App - Part 1

Add Room and then Room KMP to Android App - Part 1

·

3 min read

There are 2 stages to this post: getting Room running on Android, and then getting Room Multiplatform running on KMP so that the business logic can be shared across Android and iOS. Let’s get started.

To add Room to Android, you can refer to https://developer.android.com/training/data-storage/room - the instructions below are loosely based on that post, but with some changes to freshen it up a bit.

  1. Create a new Compose Application in Android Studio

  2. Assuming you are using a Version Catalogue, add the following to the app-level build.gradle.kts.

plugins {
    ...
    alias(libs.plugins.ksp)
}
dependencies {
    ...
    implementation(libs.androidx.room.runtime)
    ksp(libs.androidx.room.compiler)
    implementation(libs.kotlinx.coroutines.android)
    implementation(libs.androidx.lifecycle.runtime.ktx.v262)
}
  • You will need to add backing configuration to your Version Catalogue (libs.version.toml) - e.g., add ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } as a plugin, and ksp = "2.0.20-1.0.25" - then follow the yellow warnings from Android Studio to update everything to the newest versions.

  • Note that you need to include EITHER KAPT or KSP - but they are not both optional. KSP is the modern approach.

  1. Create a package called data under your main directory

  2. Create the data classes in data

class BumpCalculator {
    fun calculateBumps(golfers: List<Golfer>, holes: List<Hole>): Map<String, List<Int>> {
        // Sort the holes by difficulty, descending
        val sortedHoles = holes.sortedBy { it.difficulty }

        // Create a map to hold the result
        val golferBumps = mutableMapOf<String, List<Int>>()

        // Assign bumps for each golfer
        golfers.forEach { golfer ->
            var thisBump = golfer.bumps
            if (golfer.bumps > 18) {
                thisBump = thisBump-18
            }

            val bumps = sortedHoles.take(thisBump).map { it.number }
            golferBumps[golfer.name] = bumps
        }

        return golferBumps
    }
}
data class Golfer(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val name: String,
    val bumps: Int,
    var wins: Int,
)
data class Hole(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val number: Int,
    val difficulty: Int
)
  1. Add @entity to each of the classes (and import the Entity library)
import androidx.room.Entity
@Entity
  1. Create a DAO interface for Golfer and Hole (e.g., Dao.kt)
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query

@Dao
interface GolferDao {
    @Query("SELECT * FROM golfer")
    fun getAll(): List<Golfer>

    @Query("SELECT * FROM golfer WHERE id IN (:golferIds)")
    fun loadAllByIds(golferIds: IntArray): List<Golfer>

    @Query("SELECT * FROM golfer WHERE name LIKE :name LIMIT 1")
    fun findByName(name: String): Golfer

    @Insert
    fun insertAll(golfers: List<Golfer>)

    @Delete
    fun delete(golfer: Golfer)
}

@Dao
interface HoleDao {
    @Query("SELECT * FROM hole")
    fun getAll(): List<Hole>

    @Query("SELECT * FROM hole WHERE id IN (:holeIds)")
    fun loadAllByIds(holeIds: IntArray): List<Hole>

    @Query("SELECT * FROM hole WHERE number LIKE :holeNumber LIMIT 1")
    fun findByName(holeNumber: Int): Hole

    @Insert
    fun insertAll(holes: Hole)

    @Delete
    fun delete(hole: Hole)
}
  1. Create a Golfer and Hole Database (e.g., Database.kt)
import androidx.room.Database
import androidx.room.RoomDatabase

@Database(entities = [Golfer::class], version = 1)
abstract class GolferDatabase : RoomDatabase() {
    abstract fun golferDao(): GolferDao
}

@Database(entities = [Hole::class], version = 1)
abstract class HoleDatabase : RoomDatabase() {
    abstract fun holeDao(): HoleDao
}

Now create a reference to Room and use it within your MainActivity (e.g., right below super.onCreate

        super.onCreate(savedInstanceState)
        val db = Room.databaseBuilder(
            applicationContext,
            GolferDatabase::class.java,
            "golfer",
        ).build()
        val golferDao = db.golferDao()
        var golfers: List<Golfer> = emptyList()
        lifecycleScope.launch(Dispatchers.IO) {
            golfers = golferDao.getAll()
            if (golfers.size == 0) {
                //seed the database if it is empty
                golfers = listOf(
                    Golfer(name = "Tiger Woods", bumps = 0, wins = 82),
                    Golfer(name = "Jack Nicklaus", bumps = 0, wins = 73),
                    Golfer(name = "Matt Dyor", bumps = 0, wins = 73)
                )
                golferDao.insertAll(golfers)
            }
        }

Now we have the basics of Room operating in our Android app.

Here is the basic file structure that you should be seeing:

If you want to compare your code with the code I generated up to this point, check out this commit on GitHub: https://github.com/dyor/Room/tree/13fde538be15c34bf4fdb99b9113e7a3e6ce1fab

Now that we have an Android App with a Room database, let’s get to Part 2 where we take it multiplatform with Room KMP.