I have made a sample app which updates a piece of UI (a textview) after running a coroutine. I have very little exposure to coroutines and stuff, so I would like to get a feedback on whether my approach is correct or not, and if there is something I can add/modify to have better code writing practices. Thank you.
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.lifecycle.lifecycleScope
import com.example.coroutinesandui.databinding.ActivityMainBinding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private var count = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.counterTextView.text = "0"
binding.buttonIncrement.setOnClickListener {
Toast.makeText(binding.buttonIncrement.context,"Increasing counter",Toast.LENGTH_SHORT).show()
lifecycleScope.launch {
updateUI()
}
}
}
private suspend fun updateUI() {
count
delay(5000) // simulating database/network operation
withContext(Dispatchers.Main) {
binding.counterTextView.text = count.toString()
}
}
}
CodePudding user response:
This approach is correct, provided that when you replace delay()
with your actual work that you wrap that work either in a suspend function that delegates to an appropriate dispatcher, or wrap the work in a withContext
call that delegates to an appropriate dispatcher.
If the work is rather lengthy, more than a few seconds like a big download from the network, you would probably want to move it to the ViewModel. You could have the ViewModel return a Deferred that can be await()
ed in your Activity's suspend function, and return that same Deferred instance if the Activity (which might be a new instance after a screen rotation) asks for it again rather than restarting the job.
CodePudding user response:
When you launch coroutine in the Main threadlifecycleScope.launch {}
, you no need to change the coroutine context to update UI, because coroutine is already running in the context of Main thread.
private suspend fun updateUI() {
count
delay(5000) // simulating database/network operation
binding.counterTextView.text = count.toString()
}
If you start coroutine in the background thread lifecycleScope.launch(Dispatchers.Default) {}
,you have to change context to update UI using withContext(Dispatchers.Main)
If you think background thread is not needed for simple task ,you can launch coroutine using async{}
builder to run task concurrently .So Main thread can do some other task while delay
private suspend fun getCount():Int{
count
delay(5000)
return count }
val countDefered = async { getCount() }.await()
binding.counterTextView.text = countDefered.toString()
You can check whether the coroutine running in Main thread or some other thread using Thread.currentThread().name
lifecycleScope.launch {
Log.d("Thread name",Thread.currentThread().name)//prints main
}