For so long, we’ve been trying to determine which pattern is most suitable for use in Android development: MVP, MVVM, or even VIPER. But if changing the architecture of your current project in the middle of the path is a serious step you don’t want to take, you can migrate to a Room library as part of Android Architecture Components right now.
Like many mobile engineers who watched Google I/O last year, I was quite excited and happy about the announcement of Android Architecture Components, which contain all the necessary tools to build high-quality, testable apps. This post will be about Android Room migration from ORMLite.
It’s a modern, convenient library that’s part of Android Architecture Components, which helps you build SQLite databases without any serious headaches. It’s based on code generation with annotation usage, like Dagger 2, so if you previously used Dagger 2 or if you’re using it right now in your project, it won’t take too much effort to understand Room. As a result, you can make your projects look better and reduce your codebase size with Android Room migration. You can create entities and data access objects and set up connections between entities.
You should keep three key components in mind when you’re building a database with Room: Entity, DAO, and Database. Let’s walk through them.
@Entity
This annotation is used in classes you consider to be entities. There you describe fields and their types, choose primary keys, etc.—nothing very specific.
@Dao
This annotation is used for abstract classes in which you describe methods for performing database operations. You can perform standard operations such as @Insert, @Delete, and @Update, but you have to mark those methods with relevant annotations. You can even write your own operations using SQL syntax in the annotation body.
@Database
This is where you accumulate all the data access objects that you’ll use. After compiling the project, you can address the database class, get these DAOs, and call certain methods from them. Here you enumerate all the entities, separated by a comma, that will be in the database.
While I was working on a project for a company that gathers user data, I proposed Room migration. One of the reasons I suggested this was the frequent problems we experienced with ProGuard. I wanted to worry less about these circumstances in the future since ORMLite is based on reflection compared to Room. Our clients knew the advantages of using a Room library, so they accepted my initiative immediately and I started working on the migration from ORMLite to Google’s convenient solution.
Let me start by describing how ORMLite works. The library itself is mostly based on the annotations usage. We simply create a class which should represent an entity with fields and annotates this with @DatabaseTable annotation. Then we should create a database helper class which should be a successor of OrmLiteSqliteOpenHelper in order to get database work. We can also create any intermediates binders like DAO and other helper classes. It’s up to your needs. In our project, we create DAO’s as an intermediate class-binding between helper and entity.
I have a class annotated with @DatabaseTable(tableName=”token”). This class has two private finalized fields: id and token. They are annotated with @DatabaseField, which points to the name of the columns in the token table. For the id field, we have an annotation with a parameter (id = true), which means this field will be our primary key. Note that ORMLite requires an empty constructor to be able to compile further.
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
@DatabaseTable(tableName = "token")
public final class TokenEntity {
@DatabaseField(id = true) public String id;
@DatabaseField public String apiToken;
public TokenEntity() {
// required no-arg constructor
}
public TokenEntity(String apiToken) {
this.apiToken = apiToken;
}
}
The next step involves our data access object. We have an interface and a class called TokenDao and TokenDaoImpl. In the interface, we have two methods to take care of. One is to get the token and the second is to save.
public interface TokenDao {
TokenEntity get();
void save(TokenEntity token);
}
In the TokenDaoImpl class, we implement both these methods.
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.table.TableUtils;
public final class TokenDaoImpl implements TokenDao {
private MyDatabase db;
public TokenDaoImpl(MyDatabase database) {
this.db = database;
}
@Override public synchronized TokenEntity get() {
Dao<TokenEntity, String> dao = db.getTokenEntityDao();
try {
return dao.queryBuilder().queryForFirst();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override public synchronized void save(TokenEntity token) {
Dao<TokenEntity, String> dao = db.getTokenEntityDao();
truncateTable();
saveToken(token, dao);
}
private void saveToken(TokenEntity config, Dao<TokenEntity, String> dao) {
try {
dao.create(config);
} catch (Exception e) {
e.printStackTrace();
}
}
private void truncateTable() {
try {
TableUtils.clearTable(db.getConnectionSource(), TokenEntity.class);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Finally, we have our database helper class, which is responsible for creating and destroying tables and for creating and dropping the database.
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
import java.sql.SQLException;
import javax.inject.Inject;
import javax.inject.Singleton;
public class MyDatabase extends OrmLiteSqliteOpenHelper {
private static final String DATABASE_NAME = "MyDatabase.db";
private static final int DATABASE_VERSION = 2;
private Dao<TokenEntity, String> tokenDao;
public MyDatabase(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) {
try {
TableUtils.createTable(connectionSource, TokenEntity.class);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override public void onUpgrade(SQLiteDatabase database,
ConnectionSource connectionSource,
int oldVersion, int newVersion) {
try {
TableUtils.dropTable(connectionSource, TokenEntity.class, true);
TableUtils.createTable(connectionSource, TokenEntity.class);
} catch (Exception e) {
e.printStackTrace();
}
}
public Dao<TokenEtntiy, String> getTokenEntityDao() {
try {
if (tokenDao == null) {
tokenDao = getDao(TokenEntity.class);
}
} catch (Exception e) {
e.printStackTrace();
}
return tokenDao;
}
}
How can we simplify all the code described above? Well, let’s start with our entity. We need to rewrite it a little bit. Using Room, you need to annotate this class with @Entity, where you define the name of the table.
import android.arch.persistence.room.ColumnInfo;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;
@Entity(tableName = "token")
public class TokenEntity {
@PrimaryKey(autoGenerate = true)
public int id;
@ColumnInfo(name = "apiToken")
public String apiToken;
public TokenEntity(String token) {
this.apiToken = apiToken;
}
public String getToken() {
return apiToken;
}
}
Then we change the annotation @DatabaseField to @PrimaryKey and @ColumnInfo. Since our previous variant of the @Entity class contained two fields, we just need to define which of these fields will be the primary key in the table. It’s not hard to guess that it will be the id field. Looks simple, right? Ok, go ahead and try it yourself.
Let’s rewrite the DAO class now. The official documentation says that the DAO class should be abstract and that it doesn’t contain any business logic. When it comes to defining the operations we want to perform in the database, that may first seem like a serious drawback for Room. However, it prevents developers from polluting the code and it keeps their concerns separated, which is definitely good. It makes developers write all the additional operations in other classes, such as services, managers, etc. Room’s classes shouldn’t perform any specific business logic. It’s only responsible for performing database operations.
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.OnConflictStrategy;
import android.arch.persistence.room.Query;
@Dao
public interface TokenDao {
@Query("SELECT * FROM token LIMIT 1")
TokenEntity getToken();
@Insert(onConflict = OnConflictStrategy.REPLACE)
void saveToken(TokenEntity token);
@Query("DELETE FROM token")
void truncateTable();
}
When it comes to our database class, we just need to clean out all the code; then we make our class abstract, which is inherited from RoomDatabase. Here we just say that our database will contain TokenDao for performing operations with the token table and that in the annotation parameters we have only one entity, called TokenEntity. You can also read about all the other parameters in the official documentation.
import android.arch.persistence.room.Database;
import android.arch.persistence.room.RoomDatabase;
@Database(version = 3, entities = {
TokenEntity.class
})
public abstract class MyDatabase extends RoomDatabase {
public abstract TokenDao tokenDao();
}
To initialize the database I call a simple function in required place of the app. In my case, it will be the @Provides method of Dagger 2 module. If you don’t use Dagger 2, it can be onCreate() method of a successor of Application class. And from that moment our database was successfully migrated from ORMLite to Room.
@Provides MyDatabase providesMyDatabase(Context context) {
return Room.databaseBuilder(context, MyDatabase.class, "MyDatabase.db").build();
}
Android
Contact our team if you’re looking to outsource mobile app development.
Check out our newsletter