Room Database Operation wont crash after callling on main thread - android

I am using Coroutines to perform db operations on my Room database
I have create a helper for Coroutines as follows
object Coroutines {
fun main(work: suspend(() -> Unit)) = CoroutineScope(Dispatchers.Main).launch {
work()
}
fun io(work: suspend(() -> Unit)) = CoroutineScope(Dispatchers.IO).launch {
work()
}
}
Following is the code where i call my insert operation on the main thread
class LocalListViewModel(private val localVideoRepository: LocalVideoRepository) : ViewModel() {
val localVideos = MutableLiveData<ArrayList<LocalVideo?>?>()
fun insertAllLocalVideo() {
Coroutines.main {
localVideoRepository.insertLocalVideo(localVideos.value)
}
}
}
class LocalVideoRepository(private val db: AppDatabase) {
suspend fun insertLocalVideo(localVideos: ArrayList<LocalVideo?>?) =
db.getLocalVideoDao().insertLocalVideos(localVideos)
}
#Dao
interface LocalVideoDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertLocalVideos(localVideoList: ArrayList<LocalVideo?>?)
}
#Database(entities = [LocalVideo::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun getLocalVideoDao(): LocalVideoDao
companion object {
#Volatile
private var instance: AppDatabase? = null
private val LOCK = Any()
operator fun invoke(context: Context) = instance ?: synchronized(LOCK) {
instance ?: buildDatabase(context).also { instance = it }
}
private fun buildDatabase(context: Context) = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
context.getString(R.string.psplayer_db)
).build()
}
}
What i don't understand is even after calling on main thread of Coroutines, the data gets inserted successfully,instead of crashing?

For an in-depth answer look at the CoroutinesRoom.execute helper:
suspend fun <R> execute(
db: RoomDatabase,
inTransaction: Boolean,
callable: Callable<R>
): R {
if (db.isOpen && db.inTransaction()) {
return callable.call()
}
// Use the transaction dispatcher if we are on a transaction coroutine, otherwise
// use the database dispatchers.
val context = coroutineContext[TransactionElement]?.transactionDispatcher
?: if (inTransaction) db.transactionDispatcher else db.queryDispatcher
return withContext(context) {
callable.call()
}
}
Unless it already is in a transaction it always moves the execution of the actual query onto a IO-optimized dispatcher. So regardless of the calling context a suspend room definition will never execute on the application thread.

Related

Is there any advantage to use a viewModelScope or liveDataScope when observing LiveData from a Room database?

Can somebody please explain if there is any advantage to use a viewModelScope or liveDataScope when observing LiveData from a Room database?
My current understanding is, that I have to insert, query, delete or update data to/from a Room database from a thread that is different from the UI thread. Therefore I have been using Kotlin Coroutines with viewModelScope.launch(Dispatcher.IO) { ... } in a MVVM architecture containing a ViewModel and a Repository - similar to the setComplete function in the viewModel code below.
However, in the TasksViewModel class (from this project https://github.com/android/architecture-samples) tasksRepository.observeTask(taskId).map { computeResult(it) } returns a LiveData object and it is called without wrapping a coroutine scope around it.
Do I understand it correct, that I can omit Coroutines to observe LiveData, because LiveData runs on a background thread anyway?
If so, is there any advantage to wrap a viewModelScope or liveDataScope around tasksRepository.observeTask(taskId).map { computeResult(it) } and how should that look like in the following ViewModel?
TasksViewModel.kt
class TaskDetailViewModel(
private val tasksRepository: TasksRepository) : ViewModel() {
private val _taskId = MutableLiveData<String>()
private val _task = _taskId.switchMap { taskId ->
tasksRepository.observeTask(taskId).map { computeResult(it) }
...
fun setCompleted(completed: Boolean) = viewModelScope.launch {
val task = _task.value ?: return#launch
if (completed) {
tasksRepository.completeTask(task)
showSnackbarMessage(R.string.task_marked_complete)
} else {
tasksRepository.activateTask(task)
showSnackbarMessage(R.string.task_marked_active)
}
}
...
private fun computeResult(taskResult: Result<Task>): Task? {
return if (taskResult is Success) {
taskResult.data
} else {
showSnackbarMessage(R.string.loading_tasks_error)
null
}
}
...
}
DefaultTasksRepository.kt
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksRepository {
...
override fun observeTasks(): LiveData<Result<List<Task>>> {
return tasksLocalDataSource.observeTasks()
}
...
}
LocalTasksRepository.kt
class TasksLocalDataSource internal constructor(
private val tasksDao: TasksDao,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksDataSource {
...
override fun observeTask(taskId: String): LiveData<Result<Task>> {
return tasksDao.observeTaskById(taskId).map {
Success(it)
}
}
...
}
TasksDao.kt
#Dao
interface TasksDao {
...
#Query("SELECT * FROM Tasks WHERE entryid = :taskId")
fun observeTaskById(taskId: String): LiveData<Task>
...
}
ToDoDatabase.kt
#Database(entities = [Task::class], version = 1, exportSchema = false)
abstract class ToDoDatabase : RoomDatabase() {
abstract fun taskDao(): TasksDao
}
I hope my question is understandable?

how to save response to room database correctly?

Hi all I am developing news app I have implemented room database with kotlin coroutines what I want to achieve I want to fetch data first then that respose I want to save to room database but data not showing at all below my database implementation
#Database(entities = [Article::class], version = 1, exportSchema = false)
#TypeConverters(SourceTypeConverters::class)
abstract class SportNewsDatabase : RoomDatabase() {
abstract fun sportNewsDao(): SportNewsDao
companion object {
private var instance: SportNewsDatabase? = null
fun getInstance( context: Context): SportNewsDatabase? {
if (instance == null) {
synchronized(SportNewsDatabase::class.java) {
instance = Room.databaseBuilder(context.applicationContext, SportNewsDatabase::class.java, "article_database")
.fallbackToDestructiveMigration()
.build()
}
}
return instance
}
}
}
SportNewsDao.kt
#Dao
interface SportNewsDao {
#Query("SELECT * FROM Article")
fun getAllData(): LiveData<List<Article>>
#Insert
suspend fun addAll(article: List<Article>)
#Update
suspend fun updateArticle(article: Article)
#Delete
suspend fun deleteArticle(article: Article)
}
below NewsRepository.kt
class NewsRepository(private val sportNewsApi: SportNewsInterface, private val sportNewsDao: SportNewsDao) {
val data = sportNewsDao.getAllData()
suspend fun refresh() {
withContext(Dispatchers.IO) {
val articles = sportNewsApi.getNewsAsync().body()?.articles
if (articles != null) {
sportNewsDao.addAll(articles)
}
}
}
}
below MainViewModel.kt
#Suppress("UNCHECKED_CAST")
class MainViewModel(val newsRepository: NewsRepository) : ViewModel(), CoroutineScope {
// Coroutine's background job
val job = Job()
// Define default thread for Coroutine as Main and add job
override val coroutineContext: CoroutineContext = Dispatchers.Main + job
val showLoading = MutableLiveData<Boolean>()
val sportList = MutableLiveData<List<Article>>()
val showError = SingleLiveEvent<String>()
fun loadNews() {
// Show progressBar during the operation on the MAIN (default) thread
showLoading.value = true
// launch the Coroutine
launch {
// Switching from MAIN to IO thread for API operation
// Update our data list with the new one from API
val result = withContext(Dispatchers.IO) {
newsRepository?.data
newsRepository.refresh()
}
}
}
}
I want to know where I am making mistake what I have to do in order to save response to database correctly and show data to my android application.
This my help you debug what is going on.
You can get the created DB file on your phone with the Device File Explorer
View -> Tool Windows -> Device File Explorer.
Then navigate to where your database is and download them on your pc.
Download a SQLite DB browser and inspect the DB.

How to implement room database with coroutines correctly?

I am developing news android and I already implemented database logic with room what I want to achieve I want fetch data first and then save to room database but I have implemented that logic already when I run the application it is showing white screen only progress bar loading
below screenshot of android app
below my MainViewmModel.kt class
class MainViewModel(val newsRepository: NewsRepository) : ViewModel(), CoroutineScope {
// Coroutine's background job
val job = Job()
// Define default thread for Coroutine as Main and add job
override val coroutineContext: CoroutineContext = Dispatchers.Main + job
val showLoading = MutableLiveData<Boolean>()
val sportList = MutableLiveData<List<Article>>()
val showError = SingleLiveEvent<String>()
fun loadNews() {
// Show progressBar during the operation on the MAIN (default) thread
showLoading.value = true
// launch the Coroutine
launch {
// Switching from MAIN to IO thread for API operation
// Update our data list with the new one from API
val result = withContext(Dispatchers.IO) {
newsRepository?.data
newsRepository.refresh()
}
}
}
}
below my NewsRepository where I am fetching data and saving it to database
class NewsRepository(private val sportNewsApi: SportNewsInterface, private val sportNewsDao: SportNewsDao) {
val data = sportNewsDao.getAllData()
suspend fun refresh() {
withContext(Dispatchers.IO) {
val articles = sportNewsApi.getNewsAsync().body()?.articles
if (articles != null) {
sportNewsDao.addAll(articles)
}
}
}
}
below NewsDao.kt
#Dao
interface SportNewsDao {
#Query("SELECT * FROM Article")
fun getAllData(): LiveData<List<Article>>
#Insert
suspend fun addAll(article: List<Article>)
#Update
suspend fun updateArticle(article: Article)
#Delete
suspend fun deleteArticle(article: Article)
}
below SportNewsDatabase.kt
#Database(entities = [Article::class], version = 1, exportSchema = false)
#TypeConverters(SourceTypeConverters::class)
abstract class SportNewsDatabase : RoomDatabase() {
abstract fun sportNewsDao(): SportNewsDao
companion object {
private var instance: SportNewsDatabase? = null
fun getInstance( context: Context): SportNewsDatabase? {
if (instance == null) {
synchronized(SportNewsDatabase::class.java) {
instance = Room.databaseBuilder(context.applicationContext, SportNewsDatabase::class.java, "article_database")
.fallbackToDestructiveMigration()
.build()
}
}
return instance
}
}
}
I want to know where I am making mistake why app showing empty white screen

how save data to database after fetching data from server using room in kotlin coroutines?

I am developing news app I want to save data to database after fetching data from server using room but I am just confused I have followed tutorial but their logics is different how can I save data properly so that after getting data from server and I can save data to database?
below my SportNewsDatabase.kt
#Database(entities = [Article::class], version = 1, exportSchema = false)
abstract class SportNewsDatabase : RoomDatabase() {
abstract fun sportNewsDao(): SportNewsDao
companion object {
private var instance: SportNewsDatabase? = null
fun getInstance( context: Context): SportNewsDatabase? {
if (instance == null) {
synchronized(SportNewsDatabase::class.java) {
instance = Room.databaseBuilder(context.applicationContext, SportNewsDatabase::class.java, "article_database")
.fallbackToDestructiveMigration()
.build()
}
}
return instance
}
}
}
below SportNewsDao.kt
#Dao
interface SportNewsDao {
#Query("SELECT * FROM article")
fun getAllData(): LiveData<List<Article>>
#Insert
suspend fun addAll(article: List<Article>)
#Update
suspend fun updateArticle(article: Article)
#Delete
suspend fun deleteArticle(article: Article)
}
below my repository class
interface NewsRepository {
// Suspend is used to await the result from Deferred
suspend fun getNewsList(): UseCaseResult<List<Article>>
var sportNewsDao: SportNewsDao
}
fun insertArticle(article: List<Article>){
val insertArticles
}
#Suppress("UNCHECKED_CAST")
class NewsRepositoryImpl(private val sportsNewsApi: SportNewsInterface) : NewsRepository {
override suspend fun getNewsList(): UseCaseResult<List<Article>> {
return try {
val result = sportsNewsApi.getNewsAsync().body()!!.articles
UseCaseResult.Success(result)
} catch (ex: Exception) {
UseCaseResult.Error(ex)
}
}
}
below MainViewModel.kt where I am using Kotlin coroutines
#Suppress("UNCHECKED_CAST")
class MainViewModel(val newsRepository: NewsRepository) : ViewModel(), CoroutineScope {
// Coroutine's background job
val job = Job()
// Define default thread for Coroutine as Main and add job
override val coroutineContext: CoroutineContext = Dispatchers.Main + job
val showLoading = MutableLiveData<Boolean>()
val sportList = MutableLiveData<List<Article>>()
val showError = SingleLiveEvent<String>()
fun loadNews() {
// Show progressBar during the operation on the MAIN (default) thread
showLoading.value = true
// launch the Coroutine
launch {
// Switching from MAIN to IO thread for API operation
// Update our data list with the new one from API
val result = withContext(Dispatchers.IO) {
newsRepository?.getNewsList()
}
// Hide progressBar once the operation is done on the MAIN (default) thread
showLoading.value = false
when (result) {
is UseCaseResult.Success<*> -> {
sportList.value = result.data as List<Article>
}
is Error -> showError.value = result.message
}
}
}
override fun onCleared() {
super.onCleared()
// Clear our job when the linked activity is destroyed to avoid memory leaks
job.cancel()
}
}
#sashabeliy
Hi, i think i have the solution of your problem but i am not a expert in mvvm and coroutine so if someone have a better solution i take it :)
For my app i launch the the download in the main thread and only when i get the response i use Dispatchers.IO for insert in Room the data received.
In ViewModel
fun downLoadPDV(last_update : String) : LiveData<List<PDV>> {
return repositoryPDV.downloadPDV(last_update)
}
In Repository
fun downloadPDV(last_update : String ): LiveData<List<PDV>> {
val mutableLiveData: MutableLiveData<List<PDV>> = MutableLiveData()
val apiService: ApiInterface = RetrofitClient.getRetrofitClient().create(ApiInterface::class.java)
apiService.CallListPDV(last_update)?.enqueue(object :
Callback<List<PDV>> {
override fun onResponse(
call: Call<List<PDV>>,
response: Response<List<PDV>>
) {
Log.e(ContentValues.TAG, "Download PDV Success => $response")
if (response.isSuccessful && response.body() != null) {
mutableLiveData.setValue(response.body());
GlobalScope.launch(Dispatchers.IO) {
for (pdv in response.body()!!) {
insertPDV(pdv)
}
}
}
}
override fun onFailure(call: Call<List<PDV>>, t: Throwable) {
Log.e(ContentValues.TAG, "[Error] Download PDV Fail --> $call")
}
})
return mutableLiveData
}
I return the Data download for update the UI and inform the user of how many items was downloaded

could not figure out how to get coroutines work correctly for inserting data into Room database

I could not find the issue of not inserting the data into Room Database when app is launched first time, but when App is restarted data is inserted. Why not insertion is happening at first launch?
My code is:
#Dao
interface FooDAO {
#Insert
suspend fun save(foo: Foo)
}
and Database class is as simple:
#Database(entities = [Foo::class], version = 1, exportSchema = false)
abstract class FooDatabase: RoomDatabase() {
// get dao
abstract fun getFooDao(): FooDAO
companion object{
#Volatile private var INSTANCE: FooDatabase? = null
fun getDatabase(context: Context): FooDatabase {
if (INSTANCE == null) {
synchronized(this) {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
FooDatabase::class.java,
"my_db.db"
).fallbackToDestructiveMigration()
.addCallback(CALLBACK)
.build()
}
}
return INSTANCE!!
}
private val CALLBACK = object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
val fooDAO = INSTANCE!!.getFooDao()
CoroutineScope(Dispatchers.IO).launch {
fooDAO.save(Foo(1, "A"))
fooDAO.save(Foo(2, "B"))
fooDAO.save(Foo(3, "C"))
fooDAO.save(Foo(4, "D"))
fooDAO.save(Foo(5, "E"))
}
}
}
}
}
with the code above records are not inserted at first launch of app but when it is restarted recordes are inserted, how can we fix this issue and make it insert records at first launch as well?

Categories

Resources