I'm new to Android development and trying to understand Coroutines and LiveData
from various example projects. I have currently setup a function to call my api when the user has input a username and password. However after 1 button press, the app seems to jam and I can't make another api call as if its stuck on a pending process.
This is my first android app made with a mash of ideas so please let me know where I've made mistakes!
Activity:
binding.bLogin.setOnClickListener {
val username = binding.etUsername.text.toString()
val password = binding.etPassword.text.toString()
viewModel.userClicked(username, password).observe(this, Observer {
it?.let { resource ->
when (resource.status) {
Status.SUCCESS -> {
print(resource.data)
}
Status.ERROR -> {
print(resource.message)
}
Status.LOADING -> {
// loader stuff
}
}
}
})
}
ViewModel:
fun userClicked(username: String, password: String) = liveData(dispatcherIO) {
viewModelScope.launch {
emit(Resource.loading(data = null))
try {
userRepository.login(username, password).apply {
emit(Resource.success(null))
}
} catch (exception: Exception) {
emit(Resource.error(exception.message ?: "Error Occurred!", data = null))
}
}
}
Repository:
@WorkerThread
suspend fun login(
username: String,
password: String
): Flow<Resource<String?>> {
return flow {
emit(Resource.loading(null))
api.login(LoginRequest(username, password)).apply {
this.onSuccessSuspend {
data?.let {
prefs.apiToken = it.key
emit(Resource.success(null))
}
}
}.onErrorSuspend {
emit(Resource.error(message(), null))
}.onExceptionSuspend {
emit(Resource.error(message(), null))
}
}.flowOn(dispatcherIO)
}
API:
suspend fun login(@Body request: LoginRequest): ApiResponse<Auth>
CodePudding user response:
You don't need to launch a coroutine in liveData
builder, it is already suspend
so you can call suspend
functions there:
fun userClicked(username: String, password: String) = liveData(dispatcherIO) {
emit(Resource.loading(data = null))
try {
userRepository.login(username, password).apply {
emit(Resource.success(null))
}
} catch (exception: Exception) {
emit(Resource.error(exception.message ?: "Error Occurred!", data = null))
}
}
If you want to use LiveDate
with Flow
you can convert Flow
to LiveData
object using asLiveData
function:
fun userClicked(username: String, password: String): LiveData<Resource<String?>> {
return userRepository.login(username, password).asLiveData()
}
But I wouldn't recommend to mix up LiveData
and Flow
streams in the project. I suggest to use only Flow
.
Using only Flow
:
// In ViewModel:
fun userClicked(username: String, password: String): Flow<Resource<String?>> {
return userRepository.login(username, password)
}
// Activity
binding.bLogin.setOnClickListener {
val username = binding.etUsername.text.toString()
val password = binding.etPassword.text.toString()
lifecycleScope.launch {
viewModel.userClicked(username, password).collect { resource ->
when (resource.status) {
Status.SUCCESS -> {
print(resource.data)
}
Status.ERROR -> {
print(resource.message)
}
Status.LOADING -> {
// loader stuff
}
}
}
}
}
Remove suspend
keyword from the login
function in Repository
.