Kotlin coroutines block main thread in Android - android

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?

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.
Make the ViewModel expose a suspend function, and the activity calls it from a coroutine.
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
}
With proper viewmodel encapsulation, the Activity really shouldn't have to launch coroutines like this. The 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
}

I know internet/wifi fetching or post needs to be in somekind of background/asynctask.
Have you tried using #Background/#Uithread from Android Annotations.
It will requiere you to put some dependencies in the gradle.
But it's one way i have been dealing with services.
Here is the original link for the original DOC of it
https://github.com/androidannotations/androidannotations/wiki/WorkingWithThreads#background

Related

Why is it trying to access the database on the main thread?

I am getting this error while trying to display Room data in a LazyColumn in my project.
Cannot access database on the main thread since it may potentially lock the UI for a long period of time
I don't know why it is trying to access the database since I'm getting it with a ViewModelScope. Bellow is my MainActivity code and the ViewModel
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val users = viewModel.userList.value
LazyColumn(modifier = Modifier.fillMaxWidth()) {
items(users){data->
MyCard(data)
}
#HiltViewModel
class UserViewModel #Inject constructor(
private val repository: MainRepository
) : ViewModel() {
val userList: MutableState<List<User>> = mutableStateOf(listOf())
init {
viewModelScope.launch {
try {
val result: List<User> = repository.getAllUsers()
userList.value = result
} catch (e: Exception) {
Log.e("SSS", "${e.message.toString()}; ${e.stackTrace}")
}
}
}
Assuming you are following the pattern of your repository passing through functions from the DAO, you should mark this function in your DAO as a suspend function. This will cause it to automatically use a background thread. Then mark your corresponding repository function suspend so it can call the other suspend function.
Then in your coroutine, since getAllUsers() is a proper suspend function that internally handles proper use of background threads, there is nothing more you need to change.
The reason it gives you the warning is that by default, the viewModelScope runs on the main thread. It is up to you to wrap blocking calls in withContext(Dispatchers.IO) to run them in a background thread. But if you use suspend functions from the DAO, you don't have to worry about that because the function isn't blocking.
When launch { ... } is used without parameters, it inherits the
context (and thus dispatcher) from the CoroutineScope it is being
launched from.
Use IO thread to execute your code
viewModelScope.launch(Dispatchers.IO) {
try {
val result: List<User> = repository.getAllUsers()
userList.postValue(result)
} catch (e: Exception) {
Log.e("SSS", "${e.message.toString()}; ${e.stackTrace}")
}
}

Kotlin Flow: callbackFlow with lazy initializer of callback object

I want to use reactive paradigm using Kotlin Flow in my Android project. I have an external callback-based API so my choice is using callbackFlow in my Repository class.
I've already read insightfully some proper docs with no help:
callbackFlow documentation
Callbacks and Kotlin Flows by Roman Elizarov
What I want to achieve:
Currently my Repository class looks like this (simplified code):
lateinit var callback: ApiCallback
fun someFlow() = callbackFlow<SomeModel> {
callback = object : ApiCallback {
override fun someApiMethod() {
offer(SomeModel())
}
}
awaitClose { Log.d("Suspending flow until methods aren't invoked") }
}
suspend fun someUnfortunateCallbackDependentCall() {
externalApiClient.externalMethod(callback)
}
Problem occurs when someUnfortunateCallbackDependentCall is invoked faster than collecting someFlow().
For now to avoid UninitializedPropertyAccessException I added some delays in my coroutines before invoking someUnfortunateCallbackDependentCall but it is kind of hack/code smell for me.
My first idea was to use by lazy instead of lateinit var as this is what I want - lazy initialization of callback object. However, I couldn't manage to code it altogether. I want to emit/offer/send some data from someApiMethod to make a data flow but going outside of callbackFlow would require ProducerScope that is in it. And on the other hand, someUnfortunateCallbackDependentCall is not Kotlin Flow-based at all (could be suspended using Coroutines API at best).
Is it possible to do? Maybe using some others Kotlin delegates? Any help would be appreciated.
To answer your question technically, you can of course intialise a callback lazyily or with lateinit, but you can't do this AND share the coroutine scope (one for the Flow and one for the suspend function) at the same time - you need to build some kind of synchronisation yourself.
Below I've made some assumptions about what you are trying to achieve, perhaps they are not perfect for you, but hopefully give some incite into how to improve.
Since it is a Repository that you are creating, I will first assume that you are looking to store SomeModel and allow the rest of your app to observe changes to it. If so, the easiest way to do this is with a MutableStateFlow property instead of a callbackFlow:
interface Repository {
val state: Flow<SomeModel>
suspend fun reload()
}
class RepositoryImpl(private val service: ApiService) : Repository {
override val state = MutableStateFlow(SomeModel())
override suspend fun reload() {
return suspendCoroutine { continuation ->
service.callBackend(object : ApiCallback {
override fun someApiMethod(data: SomeModel) {
state.value = data
if (continuation.context.isActive)
continuation.resume(Unit)
}
})
}
}
}
interface ApiCallback {
fun someApiMethod(data: SomeModel)
}
data class SomeModel(val data: String = "")
interface ApiService {
fun callBackend(callback: ApiCallback)
}
The downside to this solution is that you have to call reload() in order to actually make a call to your backend, collecting the Flow alone is not enough.
myrepository.state.collect {}
myrepository.reload()
Another solution, again depending on what exactly you are trying to achieve, is to provide two ways to call your backend:
interface Repository {
fun someFlow(): Flow<SomeModel>
suspend fun reload(): SomeModel
}
class RepositoryImpl(private val service: ApiService) : Repository {
override fun someFlow() = callbackFlow<SomeModel> {
service.callBackend(object : ApiCallback {
override fun someApiMethod(data: SomeModel) {
offer(data)
}
})
awaitClose {
Log.d("TAG", "Callback Flow is closed")
}
}
override suspend fun reload(): SomeModel {
return suspendCoroutine<SomeModel> { continuation ->
service.callBackend(object : ApiCallback {
override fun someApiMethod(data: SomeModel) {
if (continuation.context.isActive)
continuation.resume(data)
}
})
}
}
}
interface ApiCallback {
fun someApiMethod(data: SomeModel)
}
data class SomeModel(val data: String = "")
interface ApiService {
fun callBackend(callback: ApiCallback)
}
Now you can either call reload() or someFlow() to retrieve SomeModel() and the Repository holds no "state".
Note that the reload() function is simply a 'coroutine' version of the callbackFlow idea.

How to observe the return value from a Repository class in a ViewModel?

I have an android application using an MVVM architecture. On a button click, I launch a coroutine that calls a ViewModel method to make a network request. In my ViewModel, I have a LiveData observable for the return of that request, but I'm not seeing it update. It seems that my repository method isn't being called and I'm not sure why.
UI Click Listener
searchButton.setOnClickListener{
CoroutineScope(IO).launch{
viewModel.getUser(username.toString())
}
}
ViewModel - Observables and invoked method
private var _user: MutableLiveData<User?> = MutableLiveData<User?>()
val user: LiveData <User?>
get() = _user
...
suspend fun getUser(userId:String) {
_user = liveData{
emit(repository.getUser(userId))
} as MutableLiveData<User?>
}
...
When I debug through, execution goes into the getUser method of the ViewModel but doesn't go into the liveData scope to update my _user MutableLiveData observable and I'm not sure what I'm doing wrong.
There is no need to use liveData coroutine builder because the getUser is a suspended function and you are already calling it in a coroutine. Just post the result simply on _user.
suspend fun getUser(userId: String) {
_user.postValue(repository.getUser(userId))
}
What you did on your code caused assigning a new instance of LiveData to _user, while the observer in the fragment is observing on previous LiveData which is instantiated by private var _user: MutableLiveData<User?> = MutableLiveData<User?>(). So, the update gets lost.
A better solution is to handle the creation of coroutines in your ViewModel class to keep track of them and prevent execution leak.
fun getUser(userId: String) {
viewModelScope.launch(IO) {
_user.postValue(repository.getUser(userId))
}
}
And in the fragment:
searchButton.setOnClickListener{
viewModel.getUser(username.toString())
}
It doesn't work because your "MVVM structure" is not following the MVVM recommendations, nor the structured concurrency guidelines provided for coroutines.
searchButton.setOnClickListener{
CoroutineScope(IO).launch{ // <-- should be using a controlled scope
viewModel.getUser(username.toString()) // <-- state belongs in the viewModel
}
}
Instead, it is supposed to look like this
searchButton.setOnClickListener {
viewModel.onSearchButtonClicked()
}
username.doAfterTextChanged {
viewModel.updateUsername(it)
}
And
class MyViewModel(
private val application: Application,
private val savedStateHandle: SavedStateHandle
): AndroidViewModel(application) {
private val repository = (application as CustomApplication).repository
private val username = savedStateHandle.getLiveData("username", "")
fun updateUsername(username: String) {
username.value = username
}
val user: LiveData<User?> = username.switchMap { userId ->
liveData(viewModelScope + Dispatchers.IO) {
emit(repository.getUser(userId))
}
}
}
Now you can do user.observe(viewLifecycleOwner) { user -> ... } and it should work. If you really do need to fetch only when the button is clicked, you might want to replace the liveData { with a regular suspend fun call, calling from viewModelScope.launch {, and save the value to a LiveData.

What is the lifetime of coroutineScope in Kotlin?

The Code A is from the project architecture samples at https://github.com/android/architecture-samples
1: I don't know if the function activateTask(task: Task) need to be wrapped with runBlocking just like Code B. I'm afraid that activateTask(task: Task) maybe not be run if the object of DefaultTasksRepository is destroyed quickly.
2: Normally I run coroutines in ViewModel.viewModelScope, I don't know whether the ViewModel.viewModelScope will be destroyed when I finish the app, and whether the coroutines running in ViewModel.viewModelScope will be destroyed too. If so, I think it will be bad, some long time coroutines such as writing data to remote server will be cancel.
3: And more, the function activateTask in Code A is a coroutines function, it can invoke another coroutines function directly, so I think the Code A+ is correct, right?
Code A
import kotlinx.coroutines.coroutineScope
...
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksRepository {
...
override suspend fun activateTask(task: Task) = withContext<Unit>(ioDispatcher) {
coroutineScope {
launch { tasksRemoteDataSource.activateTask(task) }
launch { tasksLocalDataSource.activateTask(task) }
}
}
override suspend fun clearCompletedTasks() {
coroutineScope {
launch { tasksRemoteDataSource.clearCompletedTasks() }
launch { tasksLocalDataSource.clearCompletedTasks() }
}
}
...
}
Code A+
import kotlinx.coroutines.coroutineScope
...
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksRepository {
...
override suspend fun activateTask(task: Task) = withContext<Unit>(ioDispatcher) {
tasksRemoteDataSource.activateTask(task)
tasksLocalDataSource.activateTask(task)
}
override suspend fun clearCompletedTasks() {
tasksRemoteDataSource.clearCompletedTasks()
tasksLocalDataSource.clearCompletedTasks()
}
...
}
Code B
fun main() = runBlocking {
launch {
delay(1000L)
println("World!")
}
println("Hello,")
}
You should not use runBlocking in any coroutine application, it blocks the thread.
If you really want to make activateTask non-cancellable there is a factory implementation of NonCancellable already in the stdlib
And you should not use coroutineScope wrapper inside the withContext, as a newly created CoroutineScope along with a new job is already passed as receiver within withContext.
Implement your activateTask like this:
override suspend fun activateTask(task: Task) = withContext<Unit>(ioDispatcher + NonCancellable) {
launch { tasksRemoteDataSource.activateTask(task) }
launch { tasksLocalDataSource.activateTask(task) }
}
In this way it will be called on the IODispatcher but will not be cancellable since the Job element of the resulting context does not provide functionality to cancel it.
ViewModelScope runs till your application is destroyed, more info and lifecycle chart is here. If you want to run some very important tasks, then use other dispatchers.
Yes code A+ is completely correct
PS: You should not implement runBlocking in a coroutine application, its default implementation is just the event loop.
runBlocking is the way to bridge synchronous and asynchronous code
Better implementation of main function should be:
suspend fun main() = coroutineScope {
// code here
}
It runs on the CommonPool, and if it suspends another coroutine could reuse the same thread.

I never enter inside the LiveDataScope inside my ViewModel

I have developed 2 functions for the login.
The first "loginOne" works when I use the ViewModel scope.
The other one doesn't work when I use the LiveData scope.
Do you have an idea? I want to make "loginTwo" work.
API
interface LoginAPI {
#POST("login")
suspend fun getUser(#Body loginRequest: LoginRequest): User
}
Repository
class LoginRepository(private val loginAPI: LoginAPI) {
suspend fun getUser(loginRequest: LoginRequest) = loginAPI.getUser(loginRequest)
}
ViewModel
class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel() {
private var user: LiveData<User>? = null
fun loginOne(username: String, password: String) {
viewModelScope.launch {
// i can enter here and get the user :)
val user = loginRepository.getUser(LoginRequest(username, password))
user
}
}
fun loginTwo(username: String, password: String) {
user = liveData(Dispatchers.IO) {
// i never enter inside.. why ?
val user = loginRepository.getUser(LoginRequest(username, password))
emit(user)
}
}
fun getUser(): LiveData<User>? = user
}
Fragment, my viewModel is injected with Koin
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
loginViewModel.getUser()?.observe(this, Observer { user ->
Log.d(LoginFragment::class.java.name, "User : $user ")
})
loginViewModel.loginOne("user","pcw123")
loginViewModel.loginTwo("user","pcw123")
}
Make sure that you created Scope in the right way. Also, that you are using appropriate Dispatchers to achieve wanted results.
You can additionally check if the call is being executed when you wanted to postValue.
Check if Job is still alive.
Check this thing.
Your emmit call looks suspicious.
When using LiveData, you might need to calculate values asynchronously. For example, you might want to retrieve a user's preferences and serve them to your UI. In these cases, you can use the liveData builder function to call a suspend function, serving the result as a LiveData object.
Each emit() call suspends the execution of the block until the LiveData value is set on the main thread.
In the example below, loadUser() is a suspend function declared elsewhere. Use the liveData builder function to call loadUser() asynchronously, and then use emit() to emit the result:
val user: LiveData<User> = liveData {
val data = database.loadUser() // loadUser is a suspend function.
emit(data)
}
EDIT: MutableLiveData for user variable - resolved the issue.
From the documentation:
The liveData building block serves as a structured concurrency
primitive between coroutines and LiveData. The code block starts
executing when LiveData becomes active and is automatically canceled
after a configurable timeout when the LiveData becomes inactive.
So, in your case, the 'user' liveData is already activated when you observing it from fragment. Because you called loginTwo() after liveData has been observed, the emit function will not triggered anymore. Try to call loginTwo() before observing liveData to get emit value from liveData ktx.

Categories

Resources