Issue
I am discovering Kotlin and android app dev. I fail to get data from my room
database (because of Cannot access database on the main thread
). So I try with lifecyclescope.
The concerned code, in Fragment onViewCreated
function, is :
lifecycleScope.launch {
withContext(Dispatchers.Default) {
val accountConfiguration = viewModel.get();
println("{${accountConfiguration}}")
}
}
The called function (in viewModel) is :
fun get() = viewModelScope.launch {
repository.get()
}
There is the "full" code (simplified), Entity & DAO :
@Entity
data class AccountConfiguration(
@PrimaryKey val server_address: String,
@ColumnInfo(name = "user_name") val user_name: String,
// [...]
)
@Dao
interface AccountConfigurationDao {
@Query("SELECT * FROM accountconfiguration LIMIT 1")
fun flow(): Flow<AccountConfiguration?>
@Query("SELECT * FROM accountconfiguration LIMIT 1")
suspend fun get(): AccountConfiguration?
// [...]
}
Repository :
package fr.bux.rollingdashboard
import androidx.annotation.WorkerThread
import kotlinx.coroutines.flow.Flow
class AccountConfigurationRepository(private val accountConfigurationDao: AccountConfigurationDao) {
val accountConfiguration: Flow<AccountConfiguration?> = accountConfigurationDao.flow()
// [...]
@Suppress("RedundantSuspendModifier")
@WorkerThread
suspend fun get() : AccountConfiguration? {
return accountConfigurationDao.get()
}
}
ViewModel & Factory :
class AccountConfigurationViewModel(private val repository: AccountConfigurationRepository) : ViewModel() {
val accountConfiguration: LiveData<AccountConfiguration?> = repository.accountConfiguration.asLiveData()
// [...]
fun get() = viewModelScope.launch {
repository.get()
}
// [...]
}
class AccountConfigurationViewModelFactory(private val repository: AccountConfigurationRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(AccountConfigurationViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return AccountConfigurationViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
Fragment :
class AccountConfigurationFragment : Fragment() {
private var _binding: AccountConfigurationFragmentBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
private val viewModel: AccountConfigurationViewModel by activityViewModels {
AccountConfigurationViewModelFactory(
(activity?.application as RollingDashboardApplication).account_configuration_repository
)
}
lateinit var accountConfiguration: AccountConfiguration
// [...]
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonGoBackMain.setOnClickListener {
findNavController().navigate(R.id.action_AccountConfigurationFragment_to_DashboardFragment)
}
lifecycleScope.launch {
withContext(Dispatchers.Default) {
val accountConfiguration = viewModel.get();
println("{${accountConfiguration}}")
}
}
binding.buttonSave.setOnClickListener {
save()
}
}
// [...]
}
Solution
In your current code,
lifecycleScope.launch {
withContext(Dispatchers.Default) {
val accountConfiguration = viewModel.get();
println("{${accountConfiguration}}")
}
}
viewModel.get()
is not a suspend function, so it returns immediately and proceeds to the next line. It actually returns theJob
created byviewModelScope.launch()
.- If you want your coroutine to wait for the result before continuing you should make the
get()
function suspend and return theAccountConfiguration?
suspend fun get(): AccountConfiguration? { return repository.get() }
- You need not change dispatchers to
Dispatchers.Default
because Room itself will switch to a background thread before executing any database operation. - Right now if there is a configuration change while coroutines inside
lifecyclerScope
are running, everything will get cancelled and restarted. A better way would have been to put the suspending calls inside the ViewModel and expose a LiveData/Flow to the UI.
Answered By - Arpit Shukla
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.