I get the error "java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time."
when I run Code A, why?
The Code B can work well when I replace with fun add(aMVoice: MVoice)=viewModelScope.launch (Dispatchers.IO){}
, why?
The Code C can work well when I replace with suspend fun add(aMVoice: MVoice
), why?
And more, which one is better between Code B and Code C?
Code A
@Dao
interface DBVoiceDao{
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun add(aMVoice: MVoice)
}
class DBVoiceRepository private constructor(private val mDBVoiceDao: DBVoiceDao){
suspend fun add(aMVoice: MVoice){
mDBVoiceDao.add(aMVoice)
}
}
class HomeViewModel(private val mDBVoiceRepository: DBVoiceRepository) : ViewModel() {
fun add(aMVoice: MVoice)=viewModelScope.launch{
mDBVoiceRepository.add(aMVoice)
}
}
class FragmentHome : Fragment() {
private val mHomeViewModel by lazy {...}
...
btnInsert.setOnClickListener {
val aMVoice = MVoice()
mHomeViewModel.add(aMVoice)
}
...
}
Code B
@Dao
interface DBVoiceDao{
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun add(aMVoice: MVoice)
}
class DBVoiceRepository private constructor(private val mDBVoiceDao: DBVoiceDao){
suspend fun add(aMVoice: MVoice){
mDBVoiceDao.add(aMVoice)
}
}
class HomeViewModel(private val mDBVoiceRepository: DBVoiceRepository) : ViewModel() {
fun add(aMVoice: MVoice)=viewModelScope.launch (Dispatchers.IO){ //I add Dispatchers.IO
mDBVoiceRepository.add(aMVoice)
}
}
class FragmentHome : Fragment() {
private val mHomeViewModel by lazy {...}
...
btnInsert.setOnClickListener {
val aMVoice = MVoice()
mHomeViewModel.add(aMVoice)
}
...
}
Code C
@Dao
interface DBVoiceDao{
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun add(aMVoice: MVoice) //I add suspend
}
class DBVoiceRepository private constructor(private val mDBVoiceDao: DBVoiceDao){
suspend fun add(aMVoice: MVoice){
mDBVoiceDao.add(aMVoice)
}
}
class HomeViewModel(private val mDBVoiceRepository: DBVoiceRepository) : ViewModel() {
fun add(aMVoice: MVoice)=viewModelScope.launch {
mDBVoiceRepository.add(aMVoice)
}
}
class FragmentHome : Fragment() {
private val mHomeViewModel by lazy {...}
...
btnInsert.setOnClickListener {
val aMVoice = MVoice()
mHomeViewModel.add(aMVoice)
}
...
}
}
Option A uses viewModelScope.launch
. The default dispatcher for viewModelScope
is Dispatchers.Main.immediate
as per the documentation. As add
isn't a suspending method, it runs on that dispatcher directly - i.e., it runs on the main thread.
Option B uses viewModelScope.launch(Dispatchers.IO)
which means the code runs on the IO dispatcher. As this isn't the main thread, it succeeds.
Option C makes add
a suspending function. As per the Async queries with Kotlin coroutines guide, this automatically moves the database access off of the main thread for you, no matter what dispatcher you are using. Option C is always the right technique to use when using Room + Coroutines