Read database from IO context and update UI from Main context in Kotlin Still a bit new to Kotlin and coroutines, so I want to learn best practices. The following code seems to work as expected in it's original context, though the naming has been tweaked here. The list of widgets is retrieved from the database, and the UI is updated correctly.
My main question is, in updateList is it approprate to use withContext (Dispatchers.Main) to access the UI from a job on the IO context? Feels a bit kludgy at first blush, but it does seem to work as expected.
class WidgetListActivity : AppCompatActivity() {
    private lateinit var db: AppDatabase
    private lateinit var getListJob: Job
    private lateinit var widgetList: List&lt;WidgetRecord&gt;

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_widget_list)

        runBlocking  {
            db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, &quot;Widget.db&quot;)
                .createFromAsset(&quot;database/prepop_db.sqlite&quot;).build()
        }
        val recyclerView = findViewById&lt;RecyclerView&gt;(R.id.widget_recycler_view)
        val linearLayoutManager = LinearLayoutManager(this.applicationContext)
        recyclerView.layoutManager = linearLayoutManager

        updateList()
    }

    private fun updateList() {
        getListJob = GlobalScope.launch (Dispatchers.IO) {
            widgetList = db.widgetDao().getAllWidgets()

            // HERE'S THE SECTION IN QUESTION
            withContext(Dispatchers.Main) {
                val recyclerView = findViewById&lt;RecyclerView&gt;(R.id.deck_recycler_view)
                var widgetListAdapter = WidgetListAdapter(widgetList)
                recyclerView.adapter = widgetListAdapter
                widgetListAdapter.notifyDataSetChanged()
            }
        }


    }

    override fun onDestroy() {
        runBlocking {
            getListJob.join()
        }
        
        // PLEASE NOTE: I know you wouldn't normally destroy the database
        // like this. The whole point of this Activity is simply to test
        // loading and displaying the contents of a prepopulated database.
        this.applicationContext.deleteDatabase(&quot;Widgets.db&quot;)
        super.onDestroy()
    }
}
One thing that makes it kludgy is using GlobalScope, which is discouraged by the coroutines design lead. In this case, if this were a very long-running job, you would be leaking your UI elements because they are captured by the coroutine.
Also, since you are using Room, you can make your DAO's getAllWidgets() a suspend function, in which case, there's no reason to use a specific dispatcher to call it. A properly composed suspend function doesn't block the dispatcher it is called from, so you never should have to specify a dispatcher to call a suspend function. The proper way to do this would be:
private fun updateList() {
    getListJob = lifecycleScope.launch {
        widgetList = db.widgetDao().getAllWidgets()
        val recyclerView = findViewById&lt;RecyclerView&gt;(R.id.deck_recycler_view)
        var widgetListAdapter = WidgetListAdapter(widgetList)
        recyclerView.adapter = widgetListAdapter
        widgetListAdapter.notifyDataSetChanged()
    }
}

No Dispatcher specifying needed at all, because the coroutine doesn't call any blocking code and lifecycleScope defaults to Dispatchers.Main.