Embedded Types
With type converters, we are teaching Room how to deal with custom types, but we are limited to mapping from one field to one column. That field 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 field?
In that case, we can use the @Embedded
annotation on some POJO, then use that POJO as a type in an entity.
Example: Locations
For example, as was noted earlier in this chapter, cramming a location into a single TEXT
field works, but we cannot readily query on the resulting field. 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 field.
With @Embedded
, we can, as we can see in the General/RoomEmbedded
sample project. This is a clone of the RoomTypes
project from earlier in this chapter, where we have changed Customer
to have the officeLocation
be represented by a LocationColumns
POJO:
package com.commonsware.android.room.dao;
import android.arch.persistence.room.Embedded;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.Ignore;
import android.arch.persistence.room.Index;
import android.arch.persistence.room.PrimaryKey;
import android.location.Location;
import android.support.annotation.NonNull;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
@Entity(indices={@Index(value="postalCode", unique=true)})
class Customer {
@PrimaryKey
@NonNull
public final String id;
public final String postalCode;
public final String displayName;
public final Date creationDate;
@Embedded
public final LocationColumns officeLocation;
public final Set<String> tags;
@Ignore
Customer(String postalCode, String displayName, LocationColumns officeLocation,
Set<String> tags) {
this(UUID.randomUUID().toString(), postalCode, displayName, new Date(),
officeLocation, tags);
}
Customer(String id, String postalCode, String displayName, Date creationDate,
LocationColumns officeLocation, Set<String> tags) {
this.id=id;
this.postalCode=postalCode;
this.displayName=displayName;
this.creationDate=creationDate;
this.officeLocation=officeLocation;
this.tags=tags;
}
}
The @Embedded
annotation tells Room to combine the columns from the annotated type into the table for this entity. In this case, LocationColumns
has two fields, for latitude and longitude:
package com.commonsware.android.room.dao;
public class LocationColumns {
public final double latitude;
public final double longitude;
public LocationColumns(double latitude, double longitude) {
this.latitude=latitude;
this.longitude=longitude;
}
}
LocationColumns
itself is a POJO, not an entity, though you can use @ColumnInfo
annotations if needed to rename the columns associated with the POJO’s fields.
Now, Room will use individual REAL
columns for our latitude and longitude:
CREATE TABLE IF NOT EXISTS Customer (id TEXT, postalCode TEXT, displayName TEXT, creationDate INTEGER, tags TEXT, latitude REAL, longitude REAL, PRIMARY KEY(id))
…and we can query on those:
@Query("SELECT * FROM Customer WHERE ABS(latitude-:lat)<.000001 AND ABS(longitude-:lon)<.000001")
List<Customer> findCustomersAt(double lat, double lon);
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
POJO’s field names, perhaps modified by @ColumnInfo
annotations on the POJO. In this case, though, if we have two LocationColumns
fields in the Customer
entity, 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_")
public final LocationColumns officeLocation;
The columns for that POJO will have the prefix
added:
CREATE TABLE IF NOT EXISTS Customer (id TEXT, postalCode TEXT, displayName TEXT, creationDate INTEGER, tags TEXT, office_latitude REAL, office_longitude REAL, PRIMARY KEY(id))
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
method 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.