Blibli x Android Room
Android development has entered a new era since Google brings the Android Architecture Components in Google I/O 2017. It’s such a big improvement for Android development, resulting in a separation between pre-Oreo Android development and modern Android development (Android Oreo onwards). Here’s the list of Android Architecture Components introduced by Google :
· Lifecycle-Aware Components, to produce better-organized code to maintain lifecycle status of other components, such as Activity and Fragment
· LiveData, to build data objects that notify views when the underlying database changes
· ViewModel, stores UI-related data that isn’t destroyed on app rotations
· Room, for SQLite Object Relational Mapping (ORM)
· PagedList, paging library for adapter
Not only this time but major changes on Android also happened before, such when Google first introduced the Material Design, ART to replace the old Dalvik VM, and adding support for vector image format in Android Lollipop.
Fortunately, Google made every component modularly and backward compatible, so we can minimize the cost on the existing project. In this article, I want to show you how we adopt Room; an SQLite ORM framework, on the existing MVP codebase for Blibli Seller App.
Blibli seller can search for products, orders, returns, product discussions, etc. in the app, and their search keywords will be kept in a search history. Blibli Seller App uses SQLite to save user’s history of search keywords.
Then, we have a schema like this:
The Traditional Way
Before we adopt Android Room, we create a POJO to model our schema: SearchHistory, then we create a repository class to do the SQL query and manually map the query result to the POJO. It looks like this:
fun getAllSearchHistoryData(type: String): ArrayList<SearchHistory>{
var result = ArrayList<SearchHistory>()
SQLiteDatabase db = getReadableDatabase()
val cursor = db.rawQuery(“SELECT data FROM search_history WHERE type = ?”, new String[] {type})
cursor.moveToFirst()
while (!cursor.isAfterLast) {
var searchHistory = SearchHistory();
searchHistory.id = cursor.getInt(cursor.getColumnIndex(“id”))
...
result.add(searchHistory)
cursor.moveToNext()
}
if (cursor.isAfterLast()) {
cursor.close()
}
return result;
}
The Room Way
It’s obvious that we MUST refactor our code to adopt Android Room. These are our refactoring steps:
First, we modified our SearchHistory POJO to be a Room Persistence:
@Entity(tableName = "search_history")
data class SearchHistory (
var type: String? = null,
var keyword: String? = null,
var timestamp: Long? = null
) {
@PrimaryKey(autoGenerate = true)
var id: Int? = null
}
Then, we create a new DAO (Data Access Object) for SearchHistory persistence (not POJO anymore), called SearchHistoryDAO
@Dao
interface SearchHistoryDAO {
@Query(“SELECT * FROM search_history s WHERE s.type = :type”)
fun getAllSearchHistoryData(type: String): ArrayList<SearchHistory>}
Why the DAO is an interface? This is the trick: Android Room will auto-generate the implementation class of SearchHistoryDAO at compile time, the generated class name should be SearchHistoryDAO_Impl. Actually, the implementation class will do the traditional way. But at least, we don’t have to do it ourselves :)
After that, we create an abstract class that extends RoomDatabase class, called AppDatabase. This class is responsible to register all the persistence classes, and set the SQLite database name. Advanced uses of this class are: provide data type conversions between SQLite type to Java type, database versioning, initialization and migration scripts.
@Database(entities = [SearchHistory::class], version = AppDatabase.VERSION)
abstract class AppDatabase extends RoomDatabase {
companion object {
const val VERSION = 1
const val DB_NAME = "myDb" private var dbInstance: AppDatabase? } fun getInstance(context: Context): AppDatabase? {
if (dbInstance == null) {
AppDatabase.dbInstance = Room.databaseBuilder(context, AppDatabase::class.java, AppDatabase.DB_NAME)
}
return dbInstance
}
}
Finally, because we use Dagger as our dependency injection framework, then we need to register it in the application module:
@ApplicationScope
@Provides
fun provideAppDatabase(context: Context): AppDatabase {
return AppDatabase.getInstance(context)
}
Conclusion
We see Room as a good replacement for the low-level SQLite API. The compile-time syntax checking, testability, clean APIs and familiar DAO pattern (if you have a Spring Java background) make it a breeze to use. However, it might not suitable in all cases.