Home > Software engineering >  Insert data with room from view : Cannot access database on the main thread
Insert data with room from view : Cannot access database on the main thread

Time:11-20

I am a total beginner with kotlin and android development. I followed Persist data with Room tutorial and I have a popular problem to save my data:

Cannot access database on the main thread since it may potentially lock the UI for a long period of time.

I understand why by reading other responses. But I don't understand how to deal with it.

My entity and its DAO :

@Entity
data class AccountConfiguration(
    @PrimaryKey val server_address: String,
    @ColumnInfo(name = "user_name") val user_name: String,
    @ColumnInfo(name = "password") val password: String,  // FIXME : secure storage
    @ColumnInfo(name = "notify_hungry") val notify_hungry: Boolean,
    @ColumnInfo(name = "notify_thirsty") val notify_thirsty: Boolean,
    @ColumnInfo(name = "notify_ap") val notify_ap: Boolean,
    @ColumnInfo(name = "network_grab_each") val network_grab_each: Int,
)

@Dao
interface AccountConfigurationDao {
    @Query("SELECT * FROM accountconfiguration LIMIT 1")
    fun get(): Flow<AccountConfiguration>

    @Query("DELETE FROM accountconfiguration")
    fun clear()

    @Insert
    fun insert(account_configuration: AccountConfiguration)
}

Its view model :


class AccountConfigurationViewModel(private val repository: AccountConfigurationRepository) : ViewModel() {

    val accountConfiguration: LiveData<AccountConfiguration> = repository.accountConfiguration.asLiveData()

    fun insert(account_configuration: AccountConfiguration) = viewModelScope.launch {
        repository.update(account_configuration)
    }

    fun isEntryValid(server_address: String, user_name: String, password: String, network_grab_each: Int): Boolean {
        if (server_address.isBlank() || user_name.isBlank() || password.isBlank() || network_grab_each < 5 * 60) {
            return false
        }
        return true
    }

}

The database and Application.

Then, concerned part of the view (entire file here) :

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

    // [...] hidden code

    private fun save() {
        Toast.makeText(context, R.string.saving, Toast.LENGTH_LONG).show()
        if (isEntryValid()) {
            val networkGrabEach = 3600 // FIXME : determine value for real

            viewModel.insert(
                AccountConfiguration(
                    server_address = binding.textInputServerAddress.text.toString(),
                    // [...] hidden code
                    network_grab_each = networkGrabEach,
                )
            )
            Toast.makeText(context, R.string.account_configuration_saved, Toast.LENGTH_LONG).show()
            findNavController().navigate(R.id.action_AccountConfigurationFragment_to_DashboardFragment)
        } else {
            Toast.makeText(context, R.string.wrong_inputs, Toast.LENGTH_LONG).show()
        }

    }

    // [...] hidden code
}

I don't understand how this call, viewModel.insert( can be asynchronous. Note AccountConfigurationRepository.update is already a suspend function. I tried by modifying like this without success (same error) :

-    fun insert(account_configuration: AccountConfiguration) = viewModelScope.launch {
     suspend fun insert(account_configuration: AccountConfiguration) = viewModelScope.launch {
            lifecycleScope.launch { // coroutine on Main
                viewModel.insert(
                    // ...

How can I make this database insert non blocking for UI ?

CodePudding user response:

Room DAO functions should always suspend so it can't block the main UI thread, you can refer this google recommended example: https://developer.android.com/codelabs/android-room-with-a-view-kotlin#5

CodePudding user response:

You have to make your DAO methods suspend, so they don't block the UI thread

@Dao
interface AccountConfigurationDao {
    @Query("SELECT * FROM accountconfiguration LIMIT 1")
    fun get(): Flow<AccountConfiguration>

    @Query("DELETE FROM accountconfiguration")
    suspend fun clear() //make this suspend

    @Insert
    suspend fun insert(account_configuration: AccountConfiguration) //make this suspend
}

I've tried your github code, and you've to uncomment implementation "androidx.room:room-runtime:$room_version".

I think there's a bug in Room 2.3.0 as it's giving Not sure how to handle query method's return type (java.lang.Object). error, on adding suspend keyword to DAO. You should use Room 2.4.0-beta02

def room_version = "2.4.0-beta02"
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
  • Related