I've got to update the UI with an async call to the Room Database, but when I do I've got this error : android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
// FavoritesPresenter.kt
GlobalScope.launch {
    favoritesView.showFavorites(ProductProvider.getAllProducts() as ArrayList<Product>)
}
// ProductProvider.kt
fun getAllProducts() : MutableList<Product> {
    return dao.getAllProducts()
}
// ProductDao.kt
@Query("SELECT * FROM product")
fun getAllProducts(): MutableList<Product>
What I need is to update my UI though my ProductProvider, as I'll use for all my entities I need a reliable solution.
You should fetch from Room using an IO Coroutine, and switch to a Main (UI) Coroutine to update the view.
Try:
GlobalScope.launch(Dispatchers.IO) {
            val products = ProductProvider.getAllProducts() as ArrayList<Product>
            withContext(Dispatchers.Main) {
                favoritesView.showFavorites(products)
            }
        }
Make sure to have the Android Coroutine library installed so that Main Dispatcher correctly recognises the Android Main Thread.
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"
                        Room 2.1 (currently in alpha) adds support for Kotlin coroutines. You can do the following:
Mark functions in ProductDao and ProductProvider as suspend:
// ProductDao.kt
@Query("SELECT * FROM product")
suspend fun getAllProducts(): List<Product>
// ProductProvider.kt
suspend fun getAllProducts(): List<Product> = dao.getAllProducts()
Create local scope for a coroutine in FavoritesPresenter:
class FavoritesPresenter {
    private var favoritesView: FavoritesView? = null
    private val provider: ProductProvider = ...// initialize it somehow
    private var job: Job = Job()
    private val scope = CoroutineScope(job + Dispatchers.Main)
    fun getProducts() {
        scope.launch {
            favoritesView?.showFavorites(provider.getAllProducts())
        }
    }
    fun attachView(view: FavoritesView) {
        this.favoritesView = view
    }
    fun detachView() {
        job.cancel() // cancel the job when Activity or Fragment is destroyed
        favoritesView = null
    }
    interface FavoritesView {
        fun showFavorites(products: List<Product>)
    }
}
Use FavoritesPresenter in Activity or Fragment:
class MainActivity : AppCompatActivity(), FavoritesPresenter.FavoritesView {
    lateinit var presenter: FavoritesPresenter
    override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       // ...
       presenter = FavoritesPresenter()
       presenter.attachView(this)
       presenter.getProducts()
    }
    override fun onDestroy() {
        presenter.detachView()
        super.onDestroy()
    }
    override fun showFavorites(products: List<Product>) {
        // use products to update UI
    }
}
To use Dispatchers.Main import:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
                        It would be better not use GlobalScope, instead use your own CoroutineContext, for example:
class YourActivity : CoroutineScope {
    private lateinit var job: Job
    // context for io thread
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.IO + job
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    job = Job()
  }
    fun toDoSmth() {
        launch {
            // task, do smth in io thread
            withContext(Dispatchers.Main) {
              // do smth in main thread after task is finished
            }                  
        }
    }
   override fun onDestroy() {
      job.cancel()
      super.onDestroy()
   }
}
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With