I'm trying to deliver realtime updates to my view with Kotlin Flows and Firebase.
This is how I collect my realtime data from my ViewModel:
class MainViewModel(repo: IRepo): ViewModel() {
val fetchVersionCode = liveData(Dispatchers.IO) {
emit(Resource.Loading())
try {
repo.getVersionCode().collect {
emit(it)
}
} catch (e: Exception){
emit(Resource.Failure(e))
Log.e("ERROR:", e.message)
}
}
}
And this is how I emit each flow of data from my repo whenever a value changes in Firebase:
class RepoImpl: IRepo {
override suspend fun getVersionCodeRepo(): Flow<Resource<Int>> = flow {
FirebaseFirestore.getInstance()
.collection("params").document("app").addSnapshotListener { documentSnapshot, firebaseFirestoreException ->
val versionCode = documentSnapshot!!.getLong("version")
emit(Resource.Success(versionCode!!.toInt()))
}
}
The problem is that when I use:
emit(Resource.Success(versionCode!!.toInt()))
Android Studio highlights the emit invocation with:
Suspend function 'emit' should be called only from a coroutine or another suspend function
But I'm calling this code from a CoroutineScope in my ViewModel.
What's the problem here?
thanks
A Firestore snapshot listener is effectively an asynchronous callback that runs on another thread that has nothing to do with the coroutine threads managed by Kotlin. That's why you can't call emit() inside an asynchronous callback - the callback is simply not in a coroutine context, so it can't suspend like a coroutine.
What you're trying to do requires that you put your call to emit back into a coroutine context using whatever method you see fit (e.g. launch), or perhaps start a callbackFlow that lets you offer objects from other threads.
The suspend keyword on getVersionCodeRepo() does not apply to emit(Resource.Success(versionCode!!.toInt())) because it being called from within a lambda. Since you can't change addSnapshotListener you'll need to use a coroutine builder such as launch to invoke a suspend function.
When a lambda is passed to a function, the declaration of its corresponding function parameter governs whether it can call a suspend function without a coroutine builder. For example, here is a function that takes a no-arg function parameter:
fun f(g: () -> Unit)
If this function is called like so:
f {
// do something
}
everything within the curly braces is executed as though it is within a function that is declared as:
fun g() {
// do something
}
Since g is not declared with the suspend keyword, it cannot call a suspend function without using a coroutine builder.
However, if f() is declared thus:
fun f(g: suspend () -> Unit)
and is called like so:
f {
// do something
}
everything within the curly braces is executed as though it is within a function that is declared as:
suspend fun g() {
// do something
}
Since g is declared with the suspend keyword, it can call a suspend function without using a coroutine builder.
In the case of addEventListener the lambda is being called as though it is called within a function that is declared as:
public abstract void onEvent (T value, FirebaseFirestoreException error)
Since this function declaration does not have the suspend keyword (it can't, it is written in Java) then any lambda passed to it must use a coroutine builder to call a function declared with the suspend keyword.
Related
I use livedata that collect item data, after it in onClicked() I get this data from livedata. What could be better approach for this?
lifecycleScope.launch {
lifecycleScope.async {
viewModel.fetchItem(args.workId)
}.await()
onClicked()
}
variables in viewModel
val item = _item.immutable()
private val _item = MutableLiveData<ViewState<Item?>>()
[...]
// I wait here for data from first code than I collect it because item is liveData as above
private fun onClicked() {
val item = viewModel.item.value?.dataOrNull
[...]
fetchItem method
fun fetchItem(id: Int) {
viewModelScope.launch {
_item.postAsyncValue { repository.getItem(id) }
}
}
Currently, I think your code is not doing what you think it does. fetchItem is not a suspend function. It launches a coroutine and immediately returns without waiting for the coroutine to finish. So, your async coroutine that calls it doesn't wait for that result either. There's no point in using async at all here since the code returns almost immediately.
I'm guessing that what you're trying to accomplish is wait for postAsyncValue to finish setting that new value. To do this, you need to make fetchItem a suspend function that waits for its work to be done.
I am not familiar with this postAsyncValue, but my best guess is that it is a helper extension function on MutableLiveData that takes a suspend lambda, calls it, and then sets the value to the LiveData on the main thread. If this is the case, you should just do this instead so the function actually waits for the task to be done before returning:
suspend fun fetchItem(id: Int) = withContext(Dispatchers.Main) {
_item.value = repository.getItem(id) // assuming getItem is a suspend function
}
//...
lifecycleScope.launch {
viewModel.fetchItem(args.workId)
onClicked()
}
If this isn't right, please add your source code for postAsyncValue and let me know.
Regarding your literal question, instead of using async followed immediately by await, you can use withContext(Dispatchers.IO) { }, but you would only do this if you are calling blocking code. You only need async when you're working with parallelism, so the compiler warns you that you're doing something silly if you immediately call await() after async { }.
How to await for value using Coroutine suspend method in viewModel in most efficient way.
My solution for now:
viewModelScope.launch {
val item = async { itemRepository.getItem(id) }.await()
print("log me after not null $item)
}
Kotlin converts it to and the question is also why? :)
viewModelScope.launch {
withContext(Dispatchers.Default) {
itemRepository.getItem(id)
}
print("log me after not null $item)
}
my suspend funct
suspend fun geItem(itemId: Int): ItemRoom? = getItemByIdUseCase.build(itemId)
++ method from here \/
class GetItemByIdUseCase(private val itemDao: ItemDao) :
BaseUseCase<Int, ItemRoom>() {
override suspend fun create(id: Int): ItemRoom {
return itemDao.getItemById(id)
}
}
++ method from here \/
#Query("SELECT * FROM item WHERE id=:id")
abstract suspend fun getItemById(id: Int): ItemRoom
async { /* ... */ }.await() is a more convoluted version of withContext { }. There's no reason to use async if you are going to immediately await its result, because you are ultimately using it to synchronously get a result (synchronous, but suspending). The compiler probably warns you because if you do something that doesn't really make sense or is needlessly convoluted, it's likely you're doing something wrong or don't understand what you're doing.
In this case, you don't even need withContext(), because the only thing in your lambda is a suspend function call. You can freely call suspend functions synchronously inside coroutines and other suspend functions without worrying about coroutine context (as long as these functions are non-blocking, as any suspend function generated by Room would be).
viewModelScope.launch {
val item = itemRepository.getItem(id)
print("log me after not null $item")
}
This is the core concept of a suspend function. You can call it synchronously without it blocking the current thread.
I'm guessing the compiler warning just isn't sophisticated enough to tell that you don't need withContext, since it can't 100% guarantee you aren't calling a blocking function. Maybe it just assumes you were calling something blocking if you were trying to use async.
Hey I want to call api from object class. I am new in Coroutines. I tried some code, but i am not sure is it correct way of doing it or not.
Inside LoginHelper there is function called logout have more that one function. I want to excute api call first. then i want to excute other function inside logout.
In Mainactivity I am calling LoginHelper.logout it will finish then i need to excute other line. But i don't want to make suspend function because it's using other place as well.
Also i got a errorProcess:
com.dimen.app, PID: 12496
android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1605)
Session.kt
interface Session{
#DELETE("/session/delete")
fun deleteSession(): Call<Void>
}
SessionRepository.kt
suspend fun deleteSession(): RequestResult<Void> {
return apiCall(api.deleteSession())
}
RequestResult is a Sealed Class
sealed class RequestResult<out T : Any> {
data class Success<out T : Any>(): RequestResult<T>
data class Error(): RequestResult<Nothing>()
fun result(success: (data: T?) -> Unit),error: (error: Error) -> Unit)
}
MainActivity.kt
private fun setupLogout() {
logoutButton.setOnClickListener {
LoginHelper.logout() // need to wait untill this finish
// more logic here....
}
}
LoginHelper.kt
object LoginHelper {
fun logout() {
logD("logout")
deleteSession() // need to wait untill this finish and then excute more function....
}
private fun deleteSession() {
runBlocking{
apiCall.deleteSession().execute()
}
}
}
Never use runBlocking in an Android app unless you know exactly what you're doing. It's the wrong choice 99% of the time because it defeats the purpose of using coroutines. Blocking means the current thread waits for the coroutine to run its asynchronous code. But you cannot block the main thread because that freezes the UI.
Since your LoginHelper is an object or singleton, it needs its own CoroutineScope if it's going to launch coroutines.
You can make deleteSession() a suspend function so it can call the api.deleteSession() suspend function.
You can make logout() launch a coroutine to sequentially delete the session and subsequently perform other tasks. And you can make it return the launched Job so other classes can choose whether or not to simply start the logout, or to start and wait for the logout in a coroutine.
object LoginHelper {
private val scope = CoroutineScope(SupervisorJob() + CoroutineName("LoginHelper"))
fun logout(): Job = scope.launch {
logD("logout")
deleteSession()
// .... more functions that happen after deleteSession() is complete
}
private suspend fun deleteSession() {
Tokenclass.getToken()?.let {
logE("token ::-> $it")
apiCall.deleteSession(it).execute()
}
}
}
If you want the outside class to be able to wait for the logout to complete, it can call join() on the returned Job in its own coroutine, for example:
logoutButton.setOnClickListener {
lifecycleScope.launch {
LoginHelper.logout().join()
// more logic here....
}
}
If you don't need to wait for it in the activity, you don't need to start a coroutine, and you don't need to call join().
Let me start with example code snippets
suspend fun executeLive(result: MutableLiveData<Person>) {
val response = ... //suspend api request
mediatorLiveData.removeSource(response)
mediatorLiveData.addSource(response) {
result.value = sortData(it) // sortData is also suspend function which sortData at Dispatcher.Default
}
}
In this example, sortData can't call under lambda function(in this case addSource).And also I already declare executeLive as suspend, that why suspend api request can start at first. But sortData function show compile time error
Suspend function can only be called from a coroutine body
So how do I change my code structure to solve this problems?
Update: Is there any article about this?
A lambda is generally a callback function. Callback functions are so called because we wrap a block of code in a function, and pass it to someone else (or some place else) to be executed. It is a basic inversion of control where the code is not for you to execute, but someone else to do it (example the framework).
For example when you set a onClickListener on a button, we don't know when it will get called, we pass a lambda for the framework which takes care of the user interaction to call the specified action.
In your case similarly the suspend function is not calling the sortdata, it is passing it to the mediatorLiveData object to call it in its own context. It is not necessary the lambda you passed would be called from a coroutine body, as such this is not allowed.
You can solve this by converting the mediatorLiveData.addSource call into a suspending call itself with suspendCoroutine:
suspend fun executeLive(result: MutableLiveData<Person>) {
val response = ... //suspend api request
mediatorLiveData.removeSource(response)
val data = suspendCoroutine<TypeOfData> { cont ->
mediatorLiveData.addSource(response) { cont.resume(it) }
}
result.value = sortData(data)
}
I've used TypeOfData as a placeholder for whatever the type of data emitted by response is. Note that this will only work if the you're intending for a single emission to happen, though.
If you need to track multiple values, you can experiment with callbackFlow:
suspend fun executeLive(result: MutableLiveData<Person>) {
val response = ... //suspend api request
mediatorLiveData.removeSource(response)
callbackFlow<TypeOfData> {
mediatorLiveData.addSource(response) { offer(it) }
awaitClose { mediatorLiveData.removeSource(response) }
}
.collect { result.value = sortData(it) }
}
I've recently dove into Kotlin coroutines
Since I use a lot of Google's libraries, most of the jobs is done inside Task class
Currently I'm using this extension to suspend coroutine
suspend fun <T> awaitTask(task: Task<T>): T = suspendCoroutine { continuation ->
task.addOnCompleteListener { task ->
if (task.isSuccessful) {
continuation.resume(task.result)
} else {
continuation.resumeWithException(task.exception!!)
}
}
}
But recently I've seen usage like this
suspend fun <T> awaitTask(task: Task<T>): T = suspendCoroutine { continuation ->
try {
val result = Tasks.await(task)
continuation.resume(result)
} catch (e: Exception) {
continuation.resumeWithException(e)
}
}
Is there any difference, and which one is correct?
UPD: second example isn't working, idk why
The block of code passed to suspendCoroutine { ... } should not block a thread that it is being invoked on, allowing the coroutine to be suspended. This way, the actual thread can be used for other tasks. This is a key feature that allows Kotlin coroutines to scale and to run multiple coroutines even on the single UI thread.
The first example does it correctly, because it invokes task.addOnCompleteListener (see docs) (which just adds a listener and returns immediately. That is why the first one works properly.
The second example uses Tasks.await(task) (see docs) which blocks the thread that it is being invoked on and does not return until the task is complete, so it does not allow coroutine to be properly suspended.
One of the ways to wait for a Task to complete using Kotlin Coroutines is to convert the Task object into a Deferred object by applying Task.asDeferred extension function. For example for fetching data from Firebase Database it can look like the following:
suspend fun makeRequest() {
val task: Task<DataSnapshot> = FirebaseDatabase.getInstance().reference.get()
val deferred: Deferred<DataSnapshot> = task.asDeferred()
val data: Iterable<DataSnapshot> = deferred.await().children
// ... use data
}
Dependency for Task.asDeferred():
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.5.2'
To call suspend function we need to launch a coroutine:
someCoroutineScope.launch {
makeRequest()
}
someCoroutineScope is a CoroutineScope instance. In android it can be viewModelScope in ViewModel class and lifecycleScope in Activity or Fragment, or some custom CoroutineScope instance. Dependencies:
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'