Home > Blockchain >  Using coroutines to access the database
Using coroutines to access the database

Time:05-11

So far I've been using this pattern whenever I want to access the database:

runBlocking {
    launch {
        // fetch something from the database and put it to some view
    }
}

Now that I'm diving deeper into Kotlin coroutines, I'm increasingly convinced, that this is a bad pattern. Essentially, I might as well just allowMainThreadQueries, as my pattern blocks the main thread anyway.

Unfortunately, I haven't found a proper pattern yet. How to effectively use Kotlins coroutines to access the database?

Is runBlocking the only entry point into coroutines?

Consider this scenario:

override fun onCreate() {
    setContentView(someLayout)

    // concurrently fetch something from the database and put it in some view
    // onCreate may return before this has finished

    someButton.setOnClickListener {
        // concurrently insert or update something in the database
    }
}

CodePudding user response:

You should never use runBlocking in an Android project, unless you are mixing Kotlin coroutines code with some Java code that can't use coroutines and it needs a way to call some coroutine in a blocking manner on one of its own background threads. In this case, you might use runBlocking to create a bridge function for the Java code to call, but you would never call this function from Kotlin and certainly never call it from the main thread. Calling blocking code on the main thread freezes the UI, which makes your app feel janky and risks triggering an ANR (application not responding) error.

The correct way to begin a coroutine is to use a CoroutineScope to launch your coroutine. These are already provided for you by the Android Jetpack framework for Activities, Fragments, and ViewModels.

In an Activity, use lifecycleScope.launch. In a Fragment, you should usually use viewLifecycleOwner.lifecycleScope.launch. In a ViewModel, use viewModelScope.launch.

What does using a CoroutineScope instead of runBlocking do? It prevents the long-running suspending actions (like reading the database from the disk) from blocking the main thread and freezing your UI. And it automatically cancels the long-running work when the Activity/Fragment/ViewModel is torn down, so it prevents memory leaks and wasted resources.

CodePudding user response:

Assume that you are using Room,

runBlocking and allowMainThreadQueries are usually used for Test purpose and you should never use them in release product.

what allowMainThreadQueries do is give you permission to access database from Main Thread which is you should Never do, because it may freeze the UI.

use lifecycleScope.launch to launch coroutine from Fragment/Activity or viewModelScope.launch from ViewModel, you might need to explicitly add the dependencies

def lifecycleVersion = '2.4.0'
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
// Lifecycles only (without ViewModel or LiveData)
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion")

Lifecycle release note https://developer.android.com/jetpack/androidx/releases/lifecycle

you should call the database operation from the ViewModel to prevent cancelation from configuration change, If the user rotate the screen while the operation is in progress, it'll be canceled and the result won't be cached.

In Activity/Fragment

// read data from db
lifecycleScope.launch {
   viewModel.someData.collect { 
      //do some stuff
   }
}

// insert
someButton.setOnClickListener {
   viewModel.insertToDatabase(someData)
}

In ViewModel

class MainViewModel (val dao: Dao) : ViewModel() {
  
  // you should expose your data as Observable
  val someData : Flow<List<SomeData>> = dao.getAllSomeData()
   
  fun insertToDatabase(data:SomeData) = viewModelScope.launch {
    dao.insert(data)
  }
}
// query
@Query("SELECT * FROM some_data_table")
abstract fun getAllSomeData(): Flow<List<SomeData>>
  • Related