How to update the listview from another thread with kotlin

1.1k views Asked by At

I'm learning Android studio with Kotlin, I have setup one listview in Oncreate, it list out user data, then I have another function onOptionItemSelected which add/delete user data item, the problem is : after I add/delete from another function, the data on listview cannot be updated:

Here is my code:

class MainActivity : AppCompatActivity() {
    private lateinit var listView : ListView

    override fun onCreate(savedInstanceState: Bundle?) {
.....
       listView = findViewById(R.id.listview_main)
       val adapter2 = listViews(this, array_firstname, array_lastname,array_age)
       listView.adapter = adapter2
....
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
........
                    GlobalScope.launch {
                         db1.UsersDao().insertAll(User_tb(0,firstName, lastName, userAge))
                         (listView.adapter as listViews).notifyDataSetChanged()
                             }

With this, I got error "Only the original thread that created a view hierarchy can touch its views."

I searched internet and found I need to use runOnUiThread, then I below part under "listView.adapter = adapter2", but it still does not work:

       Thread(Runnable {
            [email protected](java.lang.Runnable {
                (listView.adapter as listViews).notifyDataSetChanged()
            })
        }).start()

I guess I did not understand runOnUiThread correctly but cannot figure out how, could somebody help?

Thanks!

1

There are 1 answers

0
Tenfour04 On

You don't need to mess with Threads directly since you're using coroutines.

Replace this:

GlobalScope.launch {
    db1.UsersDao().insertAll(User_tb(0,firstName, lastName, userAge))
    (listView.adapter as listViews).notifyDataSetChanged()
}

with this:

lifecycleScope.launch {
    withContext(Dispatchers.IO) { 
        db1.UsersDao().insertAll(User_tb(0,firstName, lastName, userAge)) 
    }
    listView.adapter.notifyDataSetChanged() // your cast was unnecessary
}

lifecycleScope runs on the UI thread, except where you wrap the code using withContext to run in the background. So after the withContext block is done, it automatically returns to the main UI thread to run your last line.

This scope also gives you leak protection. If the Activity is closed before the job is done, the coroutine is automatically cancelled and the view elements can be freed to the GC. It won't try to run the last line that updates the UI.

Also, a tip: Your class names should start with a capital letter so they are clearly distinguishable from variable/property names. And they should be more descriptive. For example, I would change the listviews class name to something like UserListAdapter.