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