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.