6.
Entity Definitions
Written by Aldo Olivares
In the previous chapter, you learned the architecture behind Room. You also learned about ORMs, Jetpack Architecture Components and the advantages and disadvantages of SQLite.
In this chapter, you’ll cover all you need to know about Room entities. Along the way, you will learn:
- The properties of SQLite tables.
- How to add Room’s dependencies to your gradle files.
- How to create SQLite tables using Room annotations.
- How to define primary keys and column names.
I hope you’re ready to get started!
Note: This chapter assumes you have a basic knowledge of Kotlin and Android. If you’re new to Android, you can find a lot of beginner Android content to get you started on our site at https://www.raywenderlich.com/category/android. If you know Android, but are unfamiliar with Kotlin, take a look at our tutorial, “Kotlin for Android: An Introduction” at https://www.raywenderlich.com/174395/kotlin-for-android-an-introduction-2.
Getting started
Have you ever played trivia games and thought, “There should be a game like this but with questions for Android developers”? Well, the sample app you will be working on, DroidQuiz, is a trivia game that allows you to answer a series of questions to test your knowledge of Android.
In this chapter, you’re only going to build entities that represent the tables of your database. If you’ve had some previous experience with SQL, you probably know that this can be somewhat annoying or painstaking to accomplish. Additionally, in “Chapter 3, SQLite Database,” you saw how, with SQL syntax, it can sometimes be hard to deduce which parts of it caused an error (if by some chance you get one). The Room database, and its annotation-driven system of creating entities and queries, make this process tremendously easier.
Start by opening the starter project for this chapter in Android Studio 3.2 or greater by going to File ▸ New ▸ Import Project, and selecting the build.gradle file in the root of the project.
Once the starter project finishes loading and building, run the app on a device or an emulator.
Right now, the app is just an empty canvas, but that is about to change!
Tables and entities
Most databases consist of tables and entities. If you were to draw a parallel with object-oriented programming, you could think of entities as a class definition, which defines what the objects in the table should look like and how their respective properties behave. Once you create those objects — instances of classes, or entities in your case — you can store somewhere in memory, which is the table. The table as such is a container for all the created objects of some entity type.
This is a very brief, high-level description of how databases work, which is why it’s also beneficial to see real-life examples.
Tables
If you reached this section of the book you’re probably already familiar with relational databases, tables and SQLite. However, it is important to review other key concepts before you proceed to create your entities using Room.
Note: In this chapter, I am only going to cover the basics of database tables; if you want to learn more, check out the SQLite chapters of the previous section of the book.
Simply put, tables are structures similar to spreadsheets or two-dimensional arrays that let you store records as rows with one or more fields defined as columns — or, as mentioned above, a container for entity data.
For example, this is how a table that stores information about movies could look:
Columns represent a field or property of your data like the Title, Description, ReleaseDate or Rating. Columns usually have specific data types such as INTEGER, VARCHAR, FLOAT or DATETIME that help preserve the integrity of the information stored in your database.
For most of the tables, the first column will be used to store a primary key that uniquely identifies the record. Primary keys are values often represented as a series of integers that are incremented one by one (1,2,3,4,5,…). This is not always the case since you could also use any string that represents a unique value for each entry — like a randomly generated hash or a social security number if you have more complex security requirements. As such, the primary key is, at its core, the differentiating agent between records. You can quickly look up records by primary keys or compare two records to see if they are the same.
Primary keys also help you create relationships by defining a foreign key that references a primary key of another table. For example, say that you have an Orders table that stores the number of Blu-ray sales for each movie like below:
In this case, the MovieID is a foreign key that identifies a unique movie record in your Movies table. Therefore, if you want to retrieve extra details about an available movie, you would only need to retrieve the movie row from your Movies table with that particular ID. As such, you don’t have to store all of the data about ordered movies in the Orders table, you can just look up movie details using the foreign key. This makes each order object very light while holding enough data to look up more detailed information.
Although not all the database management systems will force you to have a primary key, it is strongly recommended that you have one for each of your tables since it makes it much easier to retrieve information using queries. But, usually, when working with databases, you can specify you want a primary key, and it gets auto-generated, so you don’t have to worry about it.
Rows represent a record in your database and can contain as many columns as needed to represent your data. Row and record are terms that are often used interchangeably just like column and field. However, when creating a database, you will need to make sure that your team is on the same page regarding naming conventions.
In the Movie table example, you have five rows each representing a different movie record added to your database. The ID column shows the primary key for each movie. Title and Description are String values, ReleaseDate is a Date field and Rating is a Float. This will help you form the appropriate entity within the Android project so that Room can create the database and tables.
Entities
To create a table using Room you need to create an entity, just like you did above. Entities in Room are defined by using a series of different annotations on classes. The following are the most common annotations that you will use when defining an entity:
@Entity
The @Entity annotation declares that the annotated class is a Room entity. For each entity, a table is created in the associated SQLite database to save your data. To add the annotation, simply use the following approach:
@Entity
data class Movie(
//...
)
The above code would create a Movie table in your database. By default, Room always uses the name of your class as the table name, but you can use the tableName property of the @Entity annotation to set a different name like shown below:
@Entity(tableName = "movies")
data class Movie(
//...
)
Now that the class is declared as an entity, you can use it in Room’s setup, which you’ll see in a bit.
@PrimaryKey
The @PrimaryKey annotation allows you to define a primary key for your table. It is very important to remember that each entity in Room must have at least one field defined as primary key:
@Entity
data class Movie(
@PrimaryKey var id: Int,
var title: String?,
var description: String?,
var releaseDate: String?,
var rating: Float
)
You can also let Room generate the primary keys automatically for you, using the autogenerate property of the @PrimaryKey annotation:
@Entity
data class Movie(
@PrimaryKey(autogenerate = true) var id: Int,
var title: String?,
var description: String?,
var releaseDate: String?,
var rating: Float
)
In the above code, the id is the primary key for your Movie table, and it is going to be automatically generated by Room incrementing the value each time by one (1,2,3,4,5,…). It is important to remember that if you set autogenerate to true, the type affinity for the field must be INTEGER, or Int in Kotlin.
@ColumnInfo
In the same way that the tableName property of the @Entity annotation allows you to customize the name of your table, the @ColumnInfo annotation lets you change the name of your columns — class properties in Kotlin:
@Entity
data class Movie(
@PrimaryKey(autogenerate = true) var id: Int,
var title: String?,
var description: String?,
@ColumnInfo(name = "release_date") var releaseDate: String?,
var rating: Float
)
This annotation is particularly useful because you will often want to follow different naming conventions for your class properties and your database columns, such as releaseDate vs release_date naming.
@Ignore
Room translates all of your class properties into database columns by default. If there is a field that you don’t want to be converted into a column, you can use the @Ignore annotation to tell Room to ignore it:
@Entity
data class Movie(
@PrimaryKey(autogenerate = true) var id: Int,
var title: String?,
var description: String?,
@ColumnInfo(name = "release_date") var releaseDate: String?,
var rating: Float,
@Ignore var poster: Bitmap?
)
In the above code, you use the @Ignore annotation to tell Room that you don’t want the poster field to be converted as a column in your Movie table. You can also use the ignoredColumn property of the @Entity annotation to declare which fields you want to ignore:
@Entity(ignoredColumns = arrayOf("poster"))
data class Movie(
@PrimaryKey(autogenerate = true) var id: Int,
var title: String?,
var description: String?,
@ColumnInfo(name = "release_date") var releaseDate: String?,
var rating: Float,
var poster: Bitmap?
)
@Embedded
The @Embedded annotation can be used on an entity’s field to tell Room that the properties on the annotated object should be represented as columns on the same entity.
For example, say that you have a User table that contains address information. Your entity could look like this:
@Entity
data class User(
@PrimaryKey val id: Int,
val firstName: String?,
val street: String?,
val state: String?,
val city: String?,
@ColumnInfo(name = "post_code") val postCode: Int
)
However, thanks to the @Embedded annotation you can represent the address fields as a separate class but Room will still generate a single table:
data class Address(
val street: String?,
val state: String?,
val city: String?,
@ColumnInfo(name = "post_code") val postCode: Int
)
@Entity
data class User(
@PrimaryKey val id: Int,
val firstName: String?,
@Embedded val address: Address?
)
Both implementations will generate a single User table with six fields: id, firstName, street, state, city and postCode.
There are more annotations available, but these are by far the most commonly used when creating your entities.
Now that you know the theory, it is time to put it into practice!
Creating your entities
Since you’ll be working on with Room, make sure you’ve opened the starter project, located in the entity-definitions ▸ projects folder.
Now, once Android studio finishes indexing the code, you need to add the appropriate dependencies to your build.gradle files to use Room. To ensure forward compatibility with future versions of Android, you will use Androidx artifacts for this project.
Open the app-level build.gradle file and add the following lines under the dependencies block:
//Room
implementation "androidx.room:room-common:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-runtime:$room_version"
Press Sync Now and wait until Android Studio finishes syncing your new dependencies.
Build and run your app one more time just to make sure that the project is still working properly after the updates to your gradle files.
Create a new package under the root directory of your app by right-clicking on droidquiz and selecting New ▸ Package and name it data.
Inside the data package, you will store all the code related to your Room database including your entities and data access objects.
Create a new package inside the data package and name it model. Inside the model package, you will store the data classes that will be converted to entities using annotations.
The DroidQuiz app will consist of a very simple database with two tables — a Question table and an Answer table.
The Question table will have two columns:
-
question_id: A self incrementing
Intthat will act as the primary key. -
text: A
Stringthat represents the text of the question.
The Answer table will have four columns:
- answer_id: The primary key for this table.
- question_id: A foreign key that references a question in the Question table.
-
is_correct: A
boolean, which indicates if this is one of the correct answers or not. -
text: A
Stringthat represents the text of the answer.
Here is how the entity-relationship diagram would look:
Each question can have one or more answers, but each answer will only have one associated question, thus creating a one-to-many relationship. Easy, right?
In this chapter, you will solely focus on defining entities, without foreign key relationships. You will learn how to create relations later on in the book.
Create a new class under the model package by right-clicking on model and selecting New ▸ Kotlin File/Class, and name it Question.
Fill or replace the file content with the following code:
@Entity(tableName = "questions") // 1
data class Question(
@PrimaryKey(autoGenerate = true) // 2
@ColumnInfo(name = "question_id") // 3
var questionId: Int,
val text: String
)
Note: Remember to use Alt + Enter on PC or Option + Return/Enter on a Mac to import any missing dependencies.
The code does the following, step by step:
- The
@Entityannotation tells Room that this data class should be converted into an entity. ThetableNameproperty indicates that the table name should be questions. -
@PrimaryKeymakes thequestionIdfield of yourQuestionclass a primary key of the table. Theautogenerateproperty declares that Room will generate the primary key for you as an auto-incremented integer. -
@ColumnInfoallows you to customize your column’s properties like the name or the type affinity -Integer,Text,Float, etc.
Now, create another class under the model package and name it Answer.
Once again, fill or replace the file content with the following code:
@Entity(tableName = "answers")
data class Answer(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "answer_id")
val answerId: Int,
@ColumnInfo(name = "question_id")
val questionId: Int,
@ColumnInfo(name = "is_correct")
val isCorrect: Boolean,
val text: String
)
The code is very similar to the Question class, so there is not much need to explain the steps. With this, your entities are ready, and it is time to create your database!
Create a new package under the data directory and name it db.
Now, create a new class under the db package and name it QuizDatabase. Replace everything inside with the following:
@Database(entities = [(Question::class), (Answer::class)], version = 1)
abstract class QuizDatabase : RoomDatabase()
Similar to your entities, Room uses the @Database annotation to define which class should be used to generate your database tables, connections and queries.
The @Database annotation should always include at least two properties:
-
entities: These should include an array of all the the entities associated with your database. -
version: This is the current version of your database, which is used to migrate everything once things change.
Note: It is very important that you declare your class as an abstract class that extends from
RoomDatabaseor Room will throw an error.
There are many ways to get an instance of your database at runtime, but, for this app, you will create a property inside your Application class.
Create a new class under the root package of your project — droidquiz — and name it QuizApplication.
Replace everything inside the QuizzApplication class with the following:
class QuizApplication : Application() {
// 1
companion object {
lateinit var database: QuizDatabase // 2
private set
}
override fun onCreate() {
super.onCreate()
database = Room
.databaseBuilder(
this,
QuizDatabase::class.java,
"quiz_database"
) // 3
.build()
}
}
Taking each commented section in turn:
- Makes this class extend
Application, so that it runs with the app launch. - Creates a private database property that will hold a reference to your Room database.
- The
databaseBuilder()method creates an instance of your Room database at runtime. The first parameter accepts aContextinstance. The second parameter expects the class that you annotated as your Room database. The third parameter allows you to define a name for your SQLite database.
Now, add your QuizApplication class to your manifest file by adding the following attribute to your application tag inside your AndroidManifest.xml file:
android:name=".QuizApplication"
Finally, build and run your app to verify that everything is still working properly:
While the changes are still not visually noticeable since you haven’t added any DAOs, you have already created your first SQLite tables using Room entities. Good job!
If you compare it to the very first implementation you’ve had with writing your SQLite helper, you understand how and why Room is becoming more and more popular among Android developers.
Key points
-
Tables are structures similar to spreadsheets or two-dimensional arrays that let you store records objects, as rows with one or more fields defined as columns.
-
The
@Entityannotation declares that the annotated class is a Room entity, and you will need to generate a table. -
The
@PrimaryKeyannotation allows you to define a primary key for your table to uniquely differentiate data. -
The
@ColumnInfoannotation lets you change the names for your columns, so you can use different naming conventions in Kotlin and in SQL. -
The
@Ignoreannotation tells Room to ignore a certain property from your class so it does not get converted into a column in the database. -
The
@Embeddedannotation can be used on an entity’s field to tell Room that the properties on the annotated object should be represented as columns on the same entity. This way you can organize your data clearly while writing the same SQL.
Where to go from here?
In this chapter, you learned a lot about SQLite tables, Room and entities. If you want to learn more about entities, I suggest the following resources:
- This official Android developer’s guide page, “Defining Data Using Room Entities,” which you can find here: https://developer.android.com/training/data-storage/room/defining-data) on how to define data using Room entities on how to define data using Room entities.
- This official Android developer’s guide page, “Entity,” which you can find here: https://developer.android.com/reference/android/arch/persistence/room/Entity.
In the next chapter, you’re going to learn even more about Room entities by creating your first relations.