I am new in Kotlin and coroutines. I have a fun
in my activity and inside it, check User
username and password and if its true, return Users
object.
every thing is Ok. but when I press button, my activity blocked and wait for response of Users
login.
I use this fun:
private fun checkLogin() : Boolean {
runBlocking {
coroutineScope {
launch {
user = viewModel.getUserAsync(login_username.text.toString(), login_password.text.toString()).await()
}
}
if(user == null){
return@runBlocking false
}
return@runBlocking true
}
return false
}
It's my ViewModel :
class LoginViewModel(app: Application) : AndroidViewModel(app) {
val context: Context = app.applicationContext
private val userService = UsersService(context)
fun getUserAsync(username: String, password: String) = GlobalScope.async {
userService.checkLogin(username, password)
}
}
UsersService:
class UsersService(ctx: Context) : IUsersService {
private val db: Database = getDatabase(ctx)
private val api = WebApiService.create()
override fun insertUser(user: Users): Long {
return db.usersDao().insertUser(user)
}
override suspend fun checkLogin(username: String, pass: String): Users? {
return api.checkLogin(username, pass)
}
}
interface IUsersService {
fun insertUser(user: Users) : Long
suspend fun checkLogin(username: String, pass: String): Users?
}
And it is my apiInterface:
interface WebApiService {
@GET("users/login")
suspend fun checkLogin(@Query("username") username: String,
@Query("password")password: String) : Users
How can I resolve issue of blocking my activity when waiting for retrieve data from server?
Coroutines is our recommended solution for asynchronous programming on Android. Noteworthy features include the following: Lightweight: You can run many coroutines on a single thread due to support for suspension, which doesn't block the thread where the coroutine is running.
You can do this by creating a property in your activity ( val scope = MainScope() ) and canceling it in onDestroy() ( scope. cancel() ).
In Kotlin coroutines, the execution is PINNED to one single CPU thread. Suspend and resume operations will make sure the coroutine will run on that same thread until the end.
Coroutines are a design pattern for writing asynchronous programs for running multiple tasks concurrently. In asynchronous programs, multiple tasks execute in parallel on separate threads without waiting for the other tasks to complete.
You should never use runBlocking
in an Android app. It is only meant to be used in the main
function of a JVM app or in a test to allow the use of coroutines that complete before the app exits. It otherwise defeats the purpose of coroutines, because it blocks until all of its lambda returns.
You also shouldn't use GlobalScope, because it makes it tricky to cancel your jobs when the Activity closes, and it starts the coroutine in a background thread instead of the main thread. You should use a local scope for the Activity. You can do this by creating a property in your activity (val scope = MainScope()
) and canceling it in onDestroy()
(scope.cancel()
). Or if you use the androidx.lifecycle:lifecycle-runtime-ktx
library you can just use the existing lifecycleScope
property.
And if you always await
your async job before returning, then your whole function will block until you get the result, so you have taken a background task and made it block the main thread.
There are a couple ways you can go about fixing this.
class LoginViewModel(app: Application) : AndroidViewModel(app) {
//...
// withContext(Dispatchers.Default) makes the suspend function do something
// on a background thread and resumes the calling thread (usually the main
// thread) when the result is ready. This is the usual way to create a simple
// suspend function. If you don't delegate to a different Dispatcher like this,
// your suspend function runs its code in the same thread that called the function
// which is not what you want for a background task.
suspend fun getUser(username: String, password: String) = withContext(Dispatchers.Default) {
userService.checkLogin(username, password)
}
}
//In your activity somewhere:
lifecycleScope.launch {
user = viewModel.getUser(login_username.text.toString(), login_password.text.toString())
// do something with user
}
user
property should be a LiveData in the ViewModel that the activity can observe. So then the coroutines only need to be launched from within the ViewModel:class LoginViewModel(app: Application) : AndroidViewModel(app) {
//...
private val _user = MutableLiveData<User>()
val user: LiveData<User> = _user
init {
fetchUser()
}
private fun fetchUser(username: String, password: String) = viewModelScope.launch {
val result = withContext(Dispatchers.Default) {
userService.checkLogin(username, password)
}
_user.value = result
}
}
//In your activity somewhere:
viewModel.user.observe(this) { user ->
// do something with user
}
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