Universally Unique IDs as a Primary Key in a Room Database

The default choice for primary key identifiers in a Room Database is usually an auto-generated Integer field, e.g. :
@PrimaryKey(autoGenerate = true) val id: IntInteger IDs are plain and straightforward to implement but not so easy to handle when it comes to apps wich can run on multiple devices at the same time or when you have remotely stored data with all its consequences (e.g. coping with collisions is a literal hell)
In many cases like the foresaid ones having Universally Unique Identifiers maight come in handy.
Here follows a brief summary of how I managed to have UUIDs as primary keys in my Room Database based apps.
Let’s assume we have a simple Room entity we use to store our app’s users, like the one below:
@Entity
data class User(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val firstName: String,
val lastName: String
)Room will auto-generate and auto-increment user IDs when you save it to the database.
Now let’s see how to achieve the same with UUIDs.
First of all we will need to convert our id to an UUID, like this:
@Entity
data class User(
@PrimaryKey val id: UUID,
val firstName: String,
val lastName: String
)But, since we don’t have a UUID field type in SQLite we will need to put some measures in place.
In the classic method we had a default zero value for our integer id, we’ll do the same for UUIDs, e.g.
@Entity(tableName = "users")
data class User(
@PrimaryKey val id: UUID = UUID.fromString(DEFAULT_UUID),
val firstName: String,
val lastName: String
) {
companion object {
const val DEFAULT_UUID = "00000000-0000-0000-0000-000000000000"
}
}Now that we have our entity let’s see how to define the app’s database.
Usually you would do so
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}Where UserDao can be defined as follows
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getAll() : List<User>
@Query("SELECT * FROM users WHERE id = :id")
fun getById(id: UUID) : User?
@Insert
fun insert(user: User)
@Update
fun update(user: User)
@Delete
fun delete(user: User)
}But then again, since UUIDs are not natively supported we will need to find our solution.
Here’s where TypeConverters come in handy.
We can define a proper TypeConverter to let Room convert UUIDs to/from strings
class Converters {
@TypeConverter
fun fromUUID(uuid: UUID): String? {
if ( uuid.toString() == User.DEFAULT_UUID )
return UUID.randomUUID().toString()
return uuid.toString()
}
@TypeConverter
fun uuidFromString(string: String?): UUID {
string?.let {
return UUID.fromString(string)
}
return UUID.fromString(User.DEFAULT_UUID)
}
}As you can see the “fromUUID” Type Converter will generate a new UUID for User instances with default identifier.
In order for these TypeConverters to do their job you will need to add it to the AppDatabase like this
@Database(entities = [User::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}Once you did so, Room will automatically convert UUIDs to/from Strings making it all work seamlessly.
val db = Room.databaseBuilder(
context, AppDatabase::class.java, "example-database"
).build()
val dao = db.userDao()
val user = User(firstName = "Paolo", lastName = "Montalto")
dao.insert(user)This is just a sample implementation, anyone can of course find the most suitable for own needs.
Your experience and opinions are welcome in the comments.






