Room is a part of the Android Architecture components which provides an abstraction layer over SQlite which allows for a more robust database acces while still providing the full power of SQlite. You can read about Room in details here.
Room 2.1 (SQLite Persistence Library) added the support for coroutines. Now you can add the suspend keyword to DAO class methods and ensures that they are not executed in the MainThread.
Coroutines are light-weight threads. They are launched with launch coroutine builder in a context of some CoroutineScope.
LiveData is an observable data holder, part of the Android Jetpack. LiveData considers an observer, which is represented by the Observer class, to be in an active state if its lifecycle is in the STARTED or RESUMED state. LiveData only notifies active observers about updates.
Dependencies
First, we need to add the needed dependencies that will enable us to use the Room library. We can do so with some simple lines in the build.gradle (Module:app) file. Last version of Room here. Last version of Lifecycle here.
apply plugin: 'kotlin-kapt'
...
dependencies {
def lifecycle_version = "2.1.0"
def room_version = "2.2.0-rc01"
def kotlin_version = "1.3.2"
// Kotlin coroutine dependencies
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_version"
// Room architecture component dependencies
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
//Live data and life cycles
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
}
Room
The Room library consists of 3 major components: Entity, DAO (Database Access Object), Database.
The Entity represents a table within the database and has to be annotated with @Enity. Each Entity consist of a minimum of one field has to define a primary key.
@Entity(tableName = "movie_items")
data class MovieEntity(
@ColumnInfo(name = "title") var title: String,
@ColumnInfo(name = "year") var year: Int
) {
@PrimaryKey(autoGenerate = true)
var id: Int = 0
}
In Room you use DAO to access and manage your data. The DAO is the main component of Room and includes methodes that offer access to your apps database it has to be annotated with @Dao. DAOs are used instead of query builders and let you seperate differend components of your database e.g. current data and statistics, which allows you to easily test your database.
@Dao
interface MovieDao {
@Query("SELECT * FROM movie_items ORDER BY title ASC")
fun getAll(): LiveData<List<MovieEntity>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(vararg movies: MovieEntity)
@Delete
suspend fun delete(movie: MovieEntity)
@Update
suspend fun update(vararg movies: MovieEntity)
}
Serves as the database holder an is the main accespoint to your relational data. It has to be annotated with @Database and extents the RoomDatabase. It also containes and returns the Dao (Database Access Object).
@Database(entities = [MovieEntity::class], version = 1)
abstract class MovieDatabase: RoomDatabase() {
abstract fun movieDao(): MovieDao
companion object {
@Volatile
private var INSTANCE: MovieDatabase? = null
fun getInstance(context: Context): MovieDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(MovieDatabase::class) {
val instance = Room.databaseBuilder(
context.applicationContext,
MovieDatabase::class.java,
"movie_database"
)
.build()
INSTANCE = instance
return instance
}
}
}
}
Repository
This is a class where we will check whether to fetch data from API or local database, or you can say we are putting the logic of database fetching in this class.
class MovieRepository(private val movieDao: MovieDao) {
val allMovies: LiveData<List<MovieEntity>> = movieDao.getAll()
suspend fun insert(movie: MovieEntity) {
movieDao.insert(movie)
}
}
ViewModel
This is the part of lifecycle library; this will help you to provide data between repository and UI. This survives the data on configuration changes and gets the existing ViewModel to reconnect with the new instance of the owner.
class MovieViewModel(application: Application): AndroidViewModel(application) {
private val repository: MovieRepository
val allMovies: LiveData<List<MovieEntity>>
init {
val movieDao = MovieDatabase.getInstance(application).movieDao()
repository = MovieRepository(movieDao)
allMovies = repository.allMovies
}
fun insert(movie: MovieEntity) = viewModelScope.launch {
repository.insert(movie)
}
}
Activity
class RoomActivity : AppCompatActivity() {
private lateinit var vm: MovieViewModel
private val TAG = "TAG"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_room)
vm = ViewModelProviders.of(this).get(MovieViewModel::class.java)
vm.allMovies.observe(this, Observer { items ->
if (items.isEmpty()) {
vm.insert(MovieEntity("Movie 1", 2001))
}
Log.d(TAG, "ITEMS: $items")
})
}
}
SQLite debug tools