Embedded Types
With type converters, we are teaching Room how to deal with custom types, but we are limited to mapping from one property to one column. That property might be complex, but it still goes into one column in the table.
What happens, though, when we have multiple columns that should combine to create a single property?
In that case, we can use the @Embedded
annotation on some data class
or other simple Kotlin class, then use that class as a type in an entity.
Example: Locations
For example, as was noted earlier in this chapter, cramming a location into a single TEXT
column works, but we cannot readily query on the resulting column. If we want to query for locations near some location in the database, it would be much more convenient to have the latitude and longitude stored as individual REAL
columns. But, using type converters, we cannot map two columns to one property.
With @Embedded
, we can, as we can see in the EmbeddedLocationEntity
class in the the MiscSamples
module:
package com.commonsware.room.misc
import android.location.Location
import android.util.JsonReader
import android.util.JsonWriter
import androidx.room.*
import org.threeten.bp.Instant
import java.io.StringReader
import java.io.StringWriter
data class LocationColumns(
val latitude: Double,
val longitude: Double
) {
constructor(loc: Location) : this(loc.latitude, loc.longitude)
}
@Entity(tableName = "embedded")
data class EmbeddedLocationEntity(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val name: String,
@Embedded
val location: LocationColumns
) {
@Dao
interface Store {
@Query("SELECT * FROM embedded")
fun loadAll(): List<EmbeddedLocationEntity>
@Insert
fun insert(entity: EmbeddedLocationEntity)
}
}
Here, we have a LocationColumns
class that wraps our latitude and longitude. The entity itself has a LocationColumns
property, named location
, marked with the @Embedded
annotation. Now, Room will use individual REAL
columns for our latitude and longitude:
CREATE TABLE IF NOT EXISTS embedded (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT NOT NULL, latitude REAL NOT NULL, longitude REAL NOT NULL)
As a result, we could construct queries on those columns, if we wished.
Note that even though a class used in @Embedded
, like LocationColumns
, is not an entity, you can still use @ColumnInfo
annotations on it to rename columns, if desired. Also, you are subject to the same rules for data types: the properties need to be types that Room understands, natively or via type converters that you create.
Simple vs. Prefixed
What happens if we need two locations, though? Perhaps we need officeLocation
and affiliateLocation
, or something like that.
By default, Room generates column names based on the @Embedded
class’ property names, perhaps modified by @ColumnInfo
annotations on those properties. In this case, though, if we have two LocationColumns
properties in the same entity class, we would wind up with two latitude
and two longitude
columns, which neither Room nor SQLite will support.
To address this, the @Embedded
annotation accepts an optional prefix
property:
@Embedded(prefix = "office_")
val officeLocation: LocationColumns
The columns for that entity will have the prefix
added:
CREATE TABLE IF NOT EXISTS embedded (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT NOT NULL, office_latitude REAL NOT NULL, office_longitude REAL NOT NULL)
Hence, having two LocationColumns
simply means that one or both need to use distinct prefix
values.
However, bear in mind that this changes the column names, so you will also need to adjust any @Query
function that references those names, so that you use the appropriate prefix.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.