Home > Back-end >  How to use LifecycleScope to execute coroutine
How to use LifecycleScope to execute coroutine

Time:11-22

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()
        }

    }

    // [...]
}

CodePudding user response:

The problem is the viewModel function :

    fun get() = viewModelScope.launch {
        repository.get()
    }

This function must be the coroutine instead launch the coroutine itself. Correct code is :

    suspend fun get(): AccountConfiguration? {
        return repository.get()
    }

CodePudding user response:

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 the Job created by viewModelScope.launch().
  • If you want your coroutine to wait for the result before continuing you should make the get() function suspend and return the AccountConfiguration?

    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.
  • Related