Elements of Android Room
By Mark Murphy
()
About this ebook
Storing data locally is a key part of many Android apps. SQLite is built into Android, but the low-level API that the Android SDK provides is a bit clunky to use.
Google’s solution for that is Room, an object wrapper around that API. This gives us a cleaner, type-safe, reactive API for our database operations. Room is part of the Android Jetpack, and so it is a key element of Google’s recommended “stack” of technologies for building Android apps.
This book explores Room, starting with basic stuff like:
- Adding Room to your app
- Defining your entities and data access operation (DAO) APIs
- Testing your database I/O
- Using Room with Kotlin coroutines, LiveData, and RxJava
- Migrating your database schema as your app evolves
It also gets into more elaborate scenarios, such as using SQLCipher for Android for encrypting your Room database.
Mark Murphy
Mark Murphy is a FranklinCovey Senior Consultant who has facilitated content successfully to clients worldwide for the last twenty-nine years. During that time, he also spent eleven years as a founding partner of a small boutique firm specializing in project management consulting. Mark grew up in Colorado and lives in Dallas, Texas.
Read more from Mark Murphy
The Leader's Guide to Unconscious Bias: How To Reframe Bias, Cultivate Connection, and Create High-Performing Teams Rating: 4 out of 5 stars4/5Hiring for Attitude (PB) Rating: 4 out of 5 stars4/5Elements of Android Jetpack Rating: 0 out of 5 stars0 ratingsTruth at Work: The Science of Delivering Tough Messages Rating: 0 out of 5 stars0 ratingsHard Goals (PB) Rating: 4 out of 5 stars4/5Exploring Android Rating: 0 out of 5 stars0 ratingsHundred Percenters: Challenge Your Employees to Give It Their All, and They'll Give You Even More Rating: 4 out of 5 stars4/5Elements of Android R Rating: 1 out of 5 stars1/5Le Guide du dirigeant: Nos biais inconscients: Corriger ses biais, cultiver ses relations et créer des équipes plus performantes Rating: 0 out of 5 stars0 ratingsElements of Android Q Rating: 0 out of 5 stars0 ratingsHow to Hire and Manage Hundred Percenters Rating: 0 out of 5 stars0 ratingsTravel Forward Rating: 0 out of 5 stars0 ratingsDumb Luck Rating: 0 out of 5 stars0 ratings
Related to Elements of Android Room
Related ebooks
How To Program A Mobile Game Rating: 4 out of 5 stars4/5Java Programming Rating: 0 out of 5 stars0 ratingsPersistence in PHP with Doctrine ORM Rating: 0 out of 5 stars0 ratingsJava: Tips and Tricks to Programming Code with Java Rating: 0 out of 5 stars0 ratingsSQLite Database Programming for Xamarin: Cross-platform C# database development for iOS and Android using SQLite.XM Rating: 0 out of 5 stars0 ratingsJava: Tips and Tricks to Programming Code with Java: Java Computer Programming, #2 Rating: 0 out of 5 stars0 ratingsYour First Week With Node.js Rating: 0 out of 5 stars0 ratingsAmazon SimpleDB: LITE Rating: 0 out of 5 stars0 ratingsExploring Android Rating: 0 out of 5 stars0 ratingsFundamentals of Android App Development: Android Development for Beginners to Learn Android Technology, SQLite, Firebase and Unity Rating: 0 out of 5 stars0 ratingsAndroid Programming For Beginners: The Simple Guide to Learning Android Programming Fast! Rating: 0 out of 5 stars0 ratingsElements of Android R Rating: 1 out of 5 stars1/5Android for Beginners: Step by Step guide to develop Android App Rating: 0 out of 5 stars0 ratingsMy First Mobile App for Students: A comprehensive guide to Android app development for beginners (English Edition) Rating: 0 out of 5 stars0 ratingsThe App Development Blueprint: A Step-by-Step Guide to Creating an App with Freelancers Rating: 0 out of 5 stars0 ratingsLearning Android Google Maps Rating: 5 out of 5 stars5/5Android Studio Development Essentials Rating: 5 out of 5 stars5/5Android Studio Hedgehog Essentials - Java Edition: Developing Android Apps Using Android Studio 2023.1.1 and Java Rating: 0 out of 5 stars0 ratingsElements of Android Q Rating: 0 out of 5 stars0 ratingsAngularJS Deployment Essentials Rating: 0 out of 5 stars0 ratingsResponsive Design High Performance Rating: 0 out of 5 stars0 ratingsProfessional Android Rating: 0 out of 5 stars0 ratingsNode.js: Tools & Skills Rating: 0 out of 5 stars0 ratingsAndroid Application Development Cookbook - Second Edition Rating: 5 out of 5 stars5/5Mastering Postman: A Comprehensive Guide to Building End-to-End APIs with Testing, Integration and Automation Rating: 0 out of 5 stars0 ratingsNode.JS Guidebook: Comprehensive guide to learn Node.js Rating: 0 out of 5 stars0 ratingsGradle for Android Rating: 0 out of 5 stars0 ratingsPHP programming Rating: 0 out of 5 stars0 ratings
Internet & Web For You
No Place to Hide: Edward Snowden, the NSA, and the U.S. Surveillance State Rating: 4 out of 5 stars4/5Wireless Hacking 101 Rating: 5 out of 5 stars5/5How to Be Invisible: Protect Your Home, Your Children, Your Assets, and Your Life Rating: 4 out of 5 stars4/5Podcasting For Dummies Rating: 4 out of 5 stars4/5Coding All-in-One For Dummies Rating: 4 out of 5 stars4/5How to Disappear and Live Off the Grid: A CIA Insider's Guide Rating: 0 out of 5 stars0 ratingsHow To Start A Profitable Authority Blog In Under One Hour Rating: 5 out of 5 stars5/5Coding For Dummies Rating: 5 out of 5 stars5/5Six Figure Blogging Blueprint Rating: 5 out of 5 stars5/5Social Engineering: The Science of Human Hacking Rating: 3 out of 5 stars3/5Everybody Lies: Big Data, New Data, and What the Internet Can Tell Us About Who We Really Are Rating: 4 out of 5 stars4/5Cybersecurity For Dummies Rating: 5 out of 5 stars5/5Get Rich or Lie Trying: Ambition and Deceit in the New Influencer Economy Rating: 0 out of 5 stars0 ratingsTor and the Dark Art of Anonymity Rating: 5 out of 5 stars5/5Grokking Algorithms: An illustrated guide for programmers and other curious people Rating: 4 out of 5 stars4/5Beginner's Guide To Starting An Etsy Print-On-Demand Shop Rating: 0 out of 5 stars0 ratingsStop Asking Questions: How to Lead High-Impact Interviews and Learn Anything from Anyone Rating: 5 out of 5 stars5/5The Gothic Novel Collection Rating: 5 out of 5 stars5/5WordPress For Dummies Rating: 0 out of 5 stars0 ratingsThe Hacker Crackdown: Law and Disorder on the Electronic Frontier Rating: 4 out of 5 stars4/5The $1,000,000 Web Designer Guide: A Practical Guide for Wealth and Freedom as an Online Freelancer Rating: 5 out of 5 stars5/5Remote/WebCam Notarization : Basic Understanding Rating: 3 out of 5 stars3/5Print On Demand Profits Rating: 4 out of 5 stars4/5How To Start A Podcast Rating: 4 out of 5 stars4/5Coding with AI For Dummies Rating: 0 out of 5 stars0 ratingsHacking : The Ultimate Comprehensive Step-By-Step Guide to the Basics of Ethical Hacking Rating: 5 out of 5 stars5/5Wordpress for Beginners: The Easy Step-by-Step Guide to Creating a Website with WordPress Rating: 5 out of 5 stars5/5JavaScript All-in-One For Dummies Rating: 5 out of 5 stars5/5HTML in 30 Pages Rating: 5 out of 5 stars5/5
Reviews for Elements of Android Room
0 ratings0 reviews
Book preview
Elements of Android Room - Mark Murphy
Room Basics
Google describes Room as providing an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.
In other words, Room aims to make your use of SQLite easier, through a lightweight annotation-based implementation of an object-relational mapping (ORM) engine.
Wrenching Relations Into Objects
If you have ever worked with a relational database — like SQLite — from an object-oriented language — like Java or Kotlin — undoubtedly you have encountered the object-relational impedance mismatch
. That is a very fancy way of saying it’s a pain getting stuff into and out of the database
.
In object-oriented programming, we are used to objects holding references to other objects, forming some sort of object graph. However, traditional SQL-style relational databases work off of tables of primitive data, using foreign keys and join tables to express relationships. Figuring out how to get our classes to map to relational tables is aggravating, and it usually results in a lot of boilerplate code.
Traditional Android development uses SQLiteDatabase for interacting with SQLite. That, in turn, uses Cursor objects to represent the results of queries and ContentValues objects to represent data to be inserted or updated. While Cursor and ContentValues are objects, they are fairly generic, much in the way that a HashMap or ArrayList is generic. In particular, neither Cursor nor ContentValues has any of our business logic. We have to somehow either wrap that around those objects or convert between those objects and some of ours.
That latter approach is what object-relational mapping engines (ORMs) take. A typical ORM works off of Java/Kotlin code and either generates a suitable database structure or works with you to identify how the classes should map to some existing table structure (e.g., a legacy one that you are stuck with). The ORM usually generates some code for you, and supplies a library, which in combination hide much of the database details from you.
The quintessential Java ORM is Hibernate. However, Hibernate was developed with server-side Java in mind and is not well-suited for slim platfoms like Android devices. However, a vast roster of Android ORMs and similar libraries have been created over the years to try to fill that gap. Some of the more popular ones have been:
SQLDelight
DBFlow
greenDAO
OrmLite
Sugar ORM
Room also helps with the object-relational impedance mismatch. It is not as deep of an ORM as some of the others, as you will be dealing with SQL a fair bit. However, Room has one huge advantage: it is from Google, and therefore it will be deemed official
in the eyes of many developers and managers.
While this book is focused on Room, you may wish to explore other ORMs if you are interested in using Java/Kotlin objects but saving the data in SQLite. Room is popular, but it is far from the only option. In particular, if you are interested in Kotlin/Multiplatform for cross-platform development, you will want to look at SQLDelight, so your database operations can also be cross-platform.
Room Requirements
To use Room, you need two dependencies in your module’s build.gradle file:
The runtime library
An annotation processor
In a Kotlin project, those will be:
room-ktx, to pull in the core Room runtime libraries plus some Kotlin-specific extensions
room-compiler, used with kapt
For example, in the NoteBasics module of the book’s primary sample project, we have a build.gradle file that pulls in those two artifacts:
apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 31
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
testInstrumentationRunner androidx.test.runner.AndroidJUnitRunner
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
} }
dependencies {
implementation org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version
implementation androidx.appcompat:appcompat:1.3.1
implementation androidx.core:core-ktx:1.6.0
implementation androidx.constraintlayout:constraintlayout:2.1.1
implementation androidx.room:room-ktx:$room_version
kapt androidx.room:room-compiler:$room_version
androidTestImplementation androidx.test.ext:junit:1.1.3
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation androidx.arch.core:core-testing:2.1.0
androidTestImplementation com.natpryce:hamkrest:1.7.0.0
}
(from NoteBasics/build.gradle)
Note that Room has a minSdkVersion requirement of API Level 15 or higher. If you attempt to build with a lower minSdkVersion, you will get a build error. If you try to override Room’s minSdkVersion using manifest merger elements, while the project will build, expect Room to crash horribly.
Room Furnishings
Roughly speaking, your use of Room is dominated by three sets of classes:
Entities, which are simple classes that model the data you are transferring into and out of the database
The data access object (DAO), that provides the description of the API that you want for working with certain entities
The database, which ties together all of the entities and DAOs for a single SQLite database
If you have used Square’s Retrofit, some of this will seem familiar:
The DAO is roughly analogous to your Retrofit interface on which you declare your Web service API
Your entities are the POJOs that you are expecting Gson/Moshi/whatever to create based on the Web service response
The NoteBasics module mentioned above has a few classes related to a note-taking application, exercised via instrumented tests.
Entities
In many ORM systems, the entity (or that system’s equivalent) is a simple class that you happen to want to store in the database. It usually represents some part of your overall domain model, so a payroll system might have entities representing departments, employees, and paychecks.
With Room, a better description of entities is that they are classes representing:
the data that you want to store into a table, and
a typical unit of a result set that you are trying to retrieve from the database
That difference may sound academic. It starts to come into play a bit more when we start thinking about relations.
However, it also more closely matches the way Retrofit maps to Web services. With Retrofit, we are not describing the contents of the Web service’s database. Rather, we are describing how we want to work with defined Web service endpoints. Those endpoints have a particular set of content that we can work with, courtesy of whoever developed the Web service. We are simply mapping those to methods and classes, both for input and output. Room is somewhere in between a Retrofit-style we just take what the Web service gives us
approach and a full ORM-style we control everything about the database
approach.
From a coding standpoint, an entity is a Java/Kotlin class marked with the @Entity annotation. For example, here is a NoteEntity class that serves as a Room entity:
package com.commonsware.room.notes
import androidx.room.Entity import androidx.room.PrimaryKey
@Entity(tableName = notes
) data class NoteEntity(
@PrimaryKey val id: String,
val title: String,
val text: String,
val version: Int )
(from NoteBasics/src/main/java/com/commonsware/room/notes/NoteEntity.kt)
There is no particular superclass required for entities, and the expectation is that often they will be simple data classes, as we see here.
The @Entity annotation can have properties customizing the behavior of your entity and how Room works with it. In this case, we have a tableName property. The default name of the SQLite table is the same as the entity class name, but tableName allows you to override that and supply your own table name. Here, we override the table name to be notes.
Sometimes, your properties will be marked with annotations describing their roles. In this example, the id field has the @PrimaryKey annotation, telling Room that this is the unique identifier for this entity. Room will use that to know how to update and delete Note objects by their primary key values. In Java, Room also requires that any @PrimaryKey field of an object type — like String — be annotated with @NonNull, as primary keys in SQLite cannot be null. In Kotlin, you can just use a non-nullable type, such as String.
We will explore entities in greater detail in an upcoming chapter.
DAO
Data access object
(DAO) is a fancy way of saying the API into the data
. The idea is that you have a DAO that provides methods for the database operations that you need: queries, inserts, updates, deletes, and so on.
In Room, the DAO is identified by the @Dao annotation, applied to either an abstract class or an interface. The actual concrete implementation will be code-generated for you by the Room annotation processor.
The primary role of the @Dao-annotated abstract class or interface is to have one or more methods, with their own Room annotations, identifying what you want to do with the database and your entities. This serves the same role as the functions annotated @GET or @POST in a Retrofit interface.
The sample app has a NoteStore that is our DAO:
package com.commonsware.room.notes
import androidx.room.*
@Dao interface NoteStore {
@Query(SELECT * FROM notes
)
fun loadAll(): List<NoteEntity>
@Insert
fun insert(note: NoteEntity)
@Update
fun update(note: NoteEntity)
@Delete
fun delete(vararg notes: NoteEntity) }
(from NoteBasics/src/main/java/com/commonsware/room/notes/NoteStore.kt)
Besides the @Dao annotation on the NoteStore interface, we have four functions, each with their own annotations: @Query, @Insert, @Update, and @Delete, each which map to the corresponding database operations.
The loadAll() function has the @Query annotation. Principally, @Query will be used for SQL SELECT statements, where you put the actual SQL in the annotation itself. Here, we are retrieving everything from the notes table.
The remaining three functions use the @Insert, @Update, and @Delete annotations, mapped to functions of the same name. The actual function names do not matter — they could be larry(), curly(), and moe() and work just as well. As you might expect, @Insert inserts an entity into our table, @Update updates an existing table row to reflect the supplied entity’s properties, and @Delete deletes table rows corresponding with the supplied entities’ primary keys. In this sample, insert() and update() each take a single NoteEntity, while delete() takes a vararg of NoteEntity. Room supports either pattern, as well as others, such as a List of NoteEntity — choose what fits your needs.
We will explore the DAO in greater detail in an upcoming chapter.
Database
In addition to entities and DAOs, you will have at least one @Database-annotated abstract class, extending a RoomDatabase base class. This class knits together the database file, the entities, and the DAOs.
In the sample project, we have a NoteDatabase serving this role:
package com.commonsware.room.notes
import androidx.room.Database import androidx.room.RoomDatabase
@Database(entities = [NoteEntity::class], version = 1) abstract class NoteDatabase : RoomDatabase() {
abstract fun notes(): NoteStore }
(from NoteBasics/src/main/java/com/commonsware/room/notes/NoteDatabase.kt)
The @Database annotation configures the code generation process, including:
Identifying all of the entity classes that you care about in the entities collection
Identifying the schema version of the database (as you see with SQLiteOpenHelper in conventional Android SQLite development)
Here, we are saying that we have just one entity class (NoteEntity), and that this is schema version 1.
You also need abstract functions for each DAO class that return an instance of that class. Here, we have a notes() function that returns NoteStore.
Get a Room
Our NoteDatabase is an abstract class. Somewhere, though, we need to get an instance of it, so we can call notes() and be able to start manipulating the database.
To create a NoteDatabase, you need a RoomDatabase.Builder. There are two functions on the Room class for getting one:
databaseBuilder(), and
inMemoryDatabaseBuilder()
databaseBuilder() will help you create a database backed by a traditional SQLite database file. inMemoryDatabaseBuilder() creates a SQLite database whose contents are only stored in memory — as soon as the database is closed, the memory holding the database contents gets freed.
Both functions take a Context and the Java Class object of your RoomDatabase subclass as parameters. databaseBuilder() also takes the name of the database file to use.
So, we could create a regular, file-backed NoteDatabase via:
private val db =
Room.databaseBuilder(context, NoteDatabase::class.java, notes.db
).build()
(where context is a suitable Context, such as the Application singleton)
While there are some configuration methods that can be called on the RoomDatabase.Builder, we skip those here, simply calling build() to build the NoteDatabase, assigning it to the db property.
From there, we can:
Call notes() on the NoteDatabase to retrieve the NoteStore DAO
Call methods on the NoteStore to query, insert, update, or delete NoteEntity objects
Testing Room
Once you have a RoomDatabase and its associated DAO(s) and entities set up, you should start testing it.
The good news is that testing Room is not dramatically different than is testing anything else in Android. Room has a few characteristics that make it a bit easier than some things to test, as it turns out.
You can learn more about testing in the Testing Your Changes
chapter of Elements of Android Jetpack!
Writing Instrumented Tests
On the whole, writing instrumented tests for Room — where the tests run on an Android device or emulator — is unremarkable. You get an instance of your RoomDatabase subclass and exercise it from there.
So, for example, here is an instrumented test case class to exercise the NoteDatabase:
package com.commonsware.room.notes
import androidx.room.Room import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.natpryce.hamkrest.assertion.assertThat import com.natpryce.hamkrest.equalTo import com.natpryce.hamkrest.hasSize import com.natpryce.hamkrest.isEmpty import org.junit.Test import org.junit.runner.RunWith import java.util.*
@RunWith(AndroidJUnit4::class) class NoteStoreTest {
private val db = Room.inMemoryDatabaseBuilder(
InstrumentationRegistry.getInstrumentation().targetContext,
NoteDatabase::class.java
)
.build()
private val underTest = db.notes()
@Test
fun insertAndDelete() {
assertThat(underTest.loadAll(), isEmpty)
val entity = NoteEntity(
id = UUID.randomUUID().toString(),
title = This is a title
,
text = This is some text
,
version = 1
)
underTest.insert(entity)
underTest.loadAll().let {
assertThat(it, hasSize(equalTo(1)))
assertThat(it[0], equalTo(entity))
}
underTest.delete(entity)
assertThat(underTest.loadAll(), isEmpty)
}
@Test
fun update() {
val entity = NoteEntity(
id = UUID.randomUUID().toString(),
title = This is a title
,
text = This is some text
,
version = 1
)
underTest.insert(entity)
val updated = entity.copy(title = This is new
, text = So is this
)
underTest.update(updated)
underTest.loadAll().let {
assertThat(it, hasSize(equalTo(1)))
assertThat(it[0], equalTo(updated))
}
} }
(from NoteBasics/src/androidTest/java/com/commonsware/room/notes/NoteStoreTest.kt)
Using In-Memory Databases
When testing a database, though, one of the challenges is in making those tests hermetic
, or self-contained. One test method should not depend upon another test method, and one test method should not affect the results of another test method accidentally. This means that we want to start with a known starting point before each test, and we have to consider how to do that.
One approach — the one taken in the above NoteStoreTest class — is to use an in-memory database. The db property is initialized using Room.inMemoryDatabaseBuilder(), so we get our fast, disposable in-memory database. For a context, we use InstrumentationRegistry.getInstrumentation().targetContext, a Context for the code being tested. We then set up underTest to be the object that we are testing: the NoteStore and its functions.
There are two key advantages for using an in-memory database for instrumented testing:
It is intrinsically self-contained. Once the NoteDatabase is closed (or garbage-collected), its memory is released, and if separate tests use separate NoteDatabase instances, one will not affect the other.
Reading and writing to and from memory is much faster than is reading and writing to and from disk, so the tests run much faster.
On the other hand, this means that the instrumented tests are useless for performance testing, as (presumably) your production app will actually store its database on disk. You could use Gradle command-line switches, custom build types and buildConfigField, or other means to decide when tests are run whether they should use memory or disk.
The Test Functions
Our test functions do things like:
Creating NoteEntity instances, using a UUID for the id
Calling insert(), update(), and delete() to manipulate the table