In February 2018 I was happy to be one of the speakers at MobOS in Cluj-Napoca. It was a nice experience, I received a lot of questions and I was impressed by the big number of Android developers. My presentation was about Room so in this article I will cover a part of the concepts included in my slides.
Handling an SQLite database in Android implies some challenges like:
- Boilerplate code
- SQL queries checked at runtime
- Database operations on the main thread
- Unmaintainable code
- Untestable code
At Google I/O 2017, the Android team launched Room Persistence Library, an SQLite object mapper as part of the Architecture Components.
What we’re providing is a set of guidelines that can help you architect an Android application to work best with the unique ways that Android interacts. (Android and Architecture)
You could check an introduction in Room at this link:
Dependencies:
In order to start our journey using Room first of all we will need to define the dependencies in our Gradle files. Check at this link the current version.
allprojects {
repositories {
jcenter()
google()
}
}
implementation
"android.arch.persistence.room:runtime:1.1.0-alpha1"
annotationProcessor
"android.arch.persistence.room:compiler:1.1.0-alpha1"
To use the latest version of Room check here the updates and also an important thing to mention is that Room works on the apps that have as minSDK API level 14.
Room contains 3 main components that help us, the developers, to have a better experience when using a database in our app:
1. Entity
@Entity(tableName = "Company")
public class Company {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
private int companyId;
@ColumnInfo(name = "name")
private String name;
@Ignore
Bitmap picture;
@Entity — by using this annotation, Room will know that this class will be a table in the database and the tableName will define the name of the table
1.1 @PrimaryKey — is used to set up the primary key of the table. We could have also a primary key that is composed of many columns and in this case, the annotation will be used on the top of the class, inside of the @Entity
1.2 @ColumnInfo — defines the name of the column from the table if we don’t want to use the name of that field
1.3 @Ignore — the field or the constructor that has this annotation will be ignored by Room, so it will not be used. Also if a field is transient, it is ignored unless it is annotated with @ColumnInfo, @Embedded or @Relation
1.4 @TypeConverter — give us the possibility to “translate” a custom type from Java/Kotlin to SQLite.
public class DateConverter {
@TypeConverter
public static Date toDate(Long timestamp) {
return timestamp == null ? null : new Date(timestamp);
}
@TypeConverter
public static Long toTimestamp(Date date) {
return date == null ? null : date.getTime();
}
}
Depending on the scenario. this @TypeConverter could be defined at different levels:
- Field level
@ColumnInfo(name = "date_updated")
@TypeConverters(DateConverter.class)
private Date itemUpdatedDate;
- Entity level
@Entity
@TypeConverters(DateConverter.class)
public class Company
- Dao level
@Dao
@TypeConverters(DateConverter.class)
public interface CompanyDao
- Database level
@Database(entities = {Company.class}, version = 1)
@TypeConverters(DateConverter.class)
public abstract class AppDatabase extends RoomDatabase
2. Dao
@Dao
public interface CompanyDao {
@Query("SELECT * FROM Company")
List getAllCompanies();
@Insert
void insertCompany(Company company);
@Update
void updateCompany(Company company);
@Delete
void deleteCompany(Company company);
}
@Dao — by using this annotation Room knows that it should provide the implementation for our interface by generating code for the defined methods. In this interface, we could define the CRUD operations for our entity and also any other operations that are necessary in order to obtain the data.
Also, Room offers us query validation at compilation time, so if we will write incorrectly the name of a table or a field we will know if after the code will be compiled.
For the next code:
@Dao
public interface DepartmentDao {
@Insert
void insertAll(List departments);
}
Room will generate the next implementation:
public class DepartmentDao_Impl implements DepartmentDao {
private final RoomDatabase __db;
public void insertAll(List departments) {
this.__db.beginTransaction();
try {
this.__insertionAdapterOfDepartment.insert(departments);
this.__db.setTransactionSuccessful();
} finally {
this.__db.endTransaction();
}
}
}
3. Database
The @Database component combines the entities and the dao interfaces.
@Database(entities = {Company.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
private static AppDatabase INSTANCE;
public abstract CompanyDao companyDao();
public static AppDatabase getAppDatabase(Context context) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class,
"company-db")
.build();
}
return INSTANCE;
}
Inside of the @Database annotation, we should define the list of the entities we want to be saved in the database and also the version of the current database. This number will help us to make the migrations in Room.
At this level, we are using an abstract class that extends RoomDatabase. Inside of it, we define the abstract methods in order to have references to our Dao classes and also we should create an instance of the database. In this case, the instance is created using the Singleton pattern and the way to generate it is similar to the Retrofit builder.
We have 2 possibilities to build the database by using:
- databaseBuilder that creates a RoomDatabase.Builder for a persistent database
- inMemoryDatabaseBuilder that creates a RoomDatabase.Builder for an in-memory database, if we don’t want to persist the data
Enjoy! Happy coding and feel free to leave a comment if something is not clear or if you have questions. 🙂
P.S.: I will create a set of articles about Room in the future. Until then you could check the introduction about this library.
Originally published at magdamiu.com on February 25, 2018.