I have still a little bit of trouble putting all information together about the thread-safety of using coroutines to launch network requests.
Let's say we have following use-case, there is a list of users we get and for each of those users, I will do some specific check which has to run over a network request to the API, giving me some information back about this user.
The userCheck happens inside a library, which doesn't expose suspend functions but rather still uses a callback.
Inside of this library, I have seen code like this to launch each of the network requests:
internal suspend fun <T> doNetworkRequest(request: suspend () -> Response<T>): NetworkResult<T> {
return withContext(Dispatchers.IO) {
try {
val response = request.invoke()
...
According to the documentation, Dispatchers.IO can use multiple threads for the execution of the code, also the request function is simply a function from a Retrofit API.
So what I did is to launch the request for each user, and use a single resultHandler object, which will add the results to a list and check if the length of the result list equals the length of the user list, if so, then all userChecks are done and I know that I can do something with the results, which need to be returned all together.
val userList: List<String>? = getUsers()
val userCheckResultList = mutableListOf<UserCheckResult>()
val handler = object : UserCheckResultHandler {
override fun onResult(
userCheckResult: UserCheckResult?
) {
userCheckResult?.let {
userCheckResultList.add(
it
)
}
if (userCheckResultList.size == userList?.size) {
doSomethingWithResultList()
print("SUCCESS")
}
}
}
userList?.forEach {
checkUser(it, handler)
}
My question is: Is this implementation thread-safe? As far as I know, Kotlin objects should be thread safe, but I have gotten feedback that this is possibly not the best implementation :D
But in theory, even if the requests get launched asynchronous and multiple at the same time, only one at a time can access the lock of the thread the result handler is running on and there will be no race condition or problems with adding items to the list and comparing the sizes.
Am I wrong about this?
Is there any way to handle this scenario in a better way?
If you are executing multiple request in parallel - it's not. List is not thread safe. But it's simple fix for that. Create a Mutex object and then just wrap your operation on list in lock, like that:
val lock = Mutex()
val userList: List<String>? = getUsers()
val userCheckResultList = mutableListOf<UserCheckResult>()
val handler = object : UserCheckResultHandler {
override fun onResult(
userCheckResult: UserCheckResult?
) {
lock.withLock {
userCheckResult?.let {
userCheckResultList.add(
it
)
}
if (userCheckResultList.size == userList?.size) {
doSomethingWithResultList()
print("SUCCESS")
}
}
}
}
userList?.forEach {
checkUser(it, handler)
}
I have to add that this whole solution seems very hacky. I would go completely other route. Run all of your requests wrapping those in async { // network request } which will return Deferred object. Add this object to some list. After that wait for all of those deferred objects using awaitAll(). Like that:
val jobs = mutableListOf<Job>()
userList?.forEach {
// i assume checkUser is suspendable here
jobs += async { checkUser(it, handler) }
}
// wait for all requests
jobs.awaitAll()
// After that you can access all results like this:
val resultOfJob0 = jobs[0].getCompleted()
Related
I'm trying to create some coroutines (async) in a loop . I want to start everything in parallel then wait for them all to finish before proceeding. The documentation provides the following example:
coroutineScope {
val deferreds = listOf( // fetch two docs at the same time
async { fetchDoc(1) }, // async returns a result for the first doc
async { fetchDoc(2) } // async returns a result for the second doc
deferreds.awaitAll() // use awaitAll to wait for both network requests
}
but this requires that all the class instantiations be known in advance. However with a varying number of instantiations this is not practical. As a work around I found that the following works:
given a mutable List of class objects from class MyObject and MyObject has a method called myDo()
private val mObjects = mutableListOf<MyObject>()
and ignoring error checking and assuming the list has 2 or more objects then the following works but it's kind of clunky and not very elegant
coroutineScope {
val pd = async { myObjects[0].myDo() }
val dds = mutableListOf(pd)
for (i in 1..numObjects - 1) {
dds.add(async {mObjects[i].myDo() })
}
val nds = dds.toList()
nds.awaitAll()
}// end coroutineScope
What I'd hope to do was something like
val dds = mutableListOf<Job>()
for (i in 0..numObjects - 1) {
dds.add(async {mObjects[i].myDo() })
}
val nds = dds.toList()
nds.awaitAll()
but this doesn't work as the async result is a
Deferred<out T> : Job
interface not a Job interface. The problem with this is in the line
val dds = mutableListOf<Job>()
I don't know what to use in place of Job. That is, for async what is T?
Any help or suggestions would be appreciated
T in this case is whatever type myDo() returns.
I think you are overcomplicating it by creating the extra MutableLists. You can do it like this:
val results = coroutineScope {
mObjects.map { obj ->
async { obj.myDo() }
}.awaitAll()
}
results will be a List<MyDoReturnType>.
Edit: I just realized, since it wasn't obvious to you that the type of a Deferred is whatever the async lambda returns, maybe it's because myDo() doesn't return anything (implicitly returns Unit). If that's the case, you should use launch instead of async. The only difference between them is that async's lambda returns something and launch's doesn't. Deferred inherits from Job because a Deferred is a Job with a result. If myDo() doesn't return anything, your code should look like the following, with no result.
coroutineScope {
for (obj in mObjects) launch { obj.myDo() }
}
The answer from TenFour04 provided the key to my answer The following code works for me
coroutineScope {
val dds = mutableListOf<Deferred<Unit>>()
for (item in mObjects) { dds.add(async {item.myDo() }) }
val nds = dds.toList()
nds.awaitAll()
}
Am I stupid!!! or what. After I figured it out, the answer is almost trivial. The best solution I found is
private val mObjects = mutableListOf<MyObject>()
coroutineScope {val deferreds = listOf(mObjects.size){async{mObjects[it].myDo()}}
deferreds.awaitAll()
}// end coroutineScope
I like this better than the map solution as it doesn't create an intermediate Pair set
I have a DAO class where I have fetchHubList method which fetches a collection of documents from cloud Firestore asynchronously using await(). This implementation used the "get()" method which I got to know later on does not fetch real-time updates. On trying to implement the code similarly using onSnapshotListener gives an error (which was quite expected to be honest, because get() and this methods return quite different things). Does anyone have any idea how to implement this?
How the code is currently:
suspend fun fetchHubList(): ArrayList<HubModel>? = try {
val hubList = ArrayList<HubModel>()
hubsListCollection.get().await().map { document ->
if (document != null) {
Log.d(TAG, "Data fetch successful!")
Log.d(TAG, "the document id is ${document.id}")
val temp = HubModel(document.get("hubName").toString(),
document.id.toString(),
document.get("isAdmin") as Boolean)
hubList.add(temp)
// hubList.add(document.toObject(HubModel::class.java))
} else {
Log.d(TAG, "No such document")
}
}
And what I want to implement here (and which is totally erroneous):
suspend fun fetchHubList(): ArrayList<HubModel>? = try {
val hubList = ArrayList<HubModel>()
hubsListCollection.addSnapshotListener().await().map { document ->
if (document != null) {
Log.d(TAG, "Data fetch successful!")
Log.d(TAG, "the document id is ${document.id}")
val temp = HubModel(document.get("hubName").toString(),
document.id.toString(),
document.get("isAdmin") as Boolean)
hubList.add(temp)
// hubList.add(document.toObject(HubModel::class.java))
} else {
Log.d(TAG, "No such document")
}
}
I use this function in my ViewModel class to create a LiveData wrapped ArrayList:
val hubList = MutableLiveData<ArrayList<HubModel>>()
private val hubListDao = HubListDao()
init {
viewModelScope.launch {
hubList.value = hubListDao.fetchHubList()
}
}
Thanks in advance!
You don't need addSnapshotListener, just use get:
hubsListCollection.get().await()
In order to observe changes in your collection you can extend LiveData:
class CafeLiveData(
private val documentReference: DocumentReference
) : LiveData<Cafe>(), EventListener<DocumentSnapshot> {
private var snapshotListener: ListenerRegistration? = null
override fun onActive() {
super.onActive()
snapshotListener = documentReference.addSnapshotListener(this)
}
override fun onInactive() {
super.onInactive()
snapshotListener?.remove()
}
override fun onEvent(result: DocumentSnapshot?, error: FirebaseFirestoreException?) {
val item = result?.let { document ->
document.toObject(Cafe::class.java)
}
value = item!!
}
}
And expose it from your view model:
fun getCafe(id: String): LiveData<Cafe> {
val query = Firebase.firestore.document("cafe/$id")
return CafeLiveData(query)
}
As #FrankvanPuffelen already mentioned in his comment, there is no way you can use ".await()" along with "addSnapshotListener()", as both are two totally different concepts. One is used to get data only once, while the second one is used to listen to real-time updates. This means that you can receive a continuous flow of data from the reference you are listening to.
Please notice that ".await()" is used in Kotlin with suspend functions. This means that when you call ".await()", you start a separate coroutine, which is a different thread that can work in parallel with other coroutines if needed. This is called async programming because ".await()" starts the coroutine execution and waits for its finish. In other words, you can use ".await()" on a deferred value to get its eventual result, if no Exception is thrown. Unfortunately, this mechanism doesn't work with real-time updates.
When it comes to Firestore, you can call ".await()" on a DocumentReference object, on a Query object, or on a CollectionReference object, which is actually a Query without filters. This means that you are waiting for the result/results to be available. So you can get a document or multiple documents from such calls. However, the following call:
hubsListCollection.addSnapshotListener().await()
Won't work, as "addSnapshotListener()" method returns a ListenerRegistration object.
I want to use a snapshot listener to listen to changes that might occur in my database to update my RecyclerView
In this case, you should consider using a library called Firebase-UI for Android. In this case, all the heavy work will be done behind the scenes. So there is no need for any coroutine or ".await()" calls, everything is synched in real-time.
If you don't want to use either Kotlin Coroutines, nor Firebase-UI Library, you can use LiveData. A concrete example can be seen in my following repo:
https://github.com/alexmamo/FirestoreRealtimePagination/blob/master/app/src/main/java/ro/alexmamo/firestorerealtimepagination/ProductListLiveData.java
Where you can subclass LiveData class and implement EventListener the interface.
I'm investigating the use of Kotlin Flow within my current Android application
My application retrieves its data from a remote server via Retrofit API calls.
Some of these API's return 50,000 data items in 500 item pages.
Each API response contains an HTTP Link header containing the Next pages complete URL.
These calls can take up to 2 seconds to complete.
In an attempt to reduce the elapsed time I have employed a Kotlin Flow to concurrently process each page
of data while also making the next page API call.
My flow is defined as follows:
private val persistenceThreadPool = Executors.newFixedThreadPool(3).asCoroutineDispatcher()
private val internalWorkWorkState = MutableStateFlow<Response<List<MyPage>>?>(null)
private val workWorkState = internalWorkWorkState.asStateFlow()
private val myJob: Job
init {
myJob = GlobalScope.launch(persistenceThreadPool) {
workWorkState.collect { page ->
if (page == null) {
} else managePage(page!!)
}
}
}
My Recursive function is defined as follows that fetches all pages:-
private suspend fun managePages(accessToken: String, response: Response<List<MyPage>>) {
when {
result != null -> return
response.isSuccessful -> internalWorkWorkState.emit(response)
else -> {
manageError(response.errorBody())
result = Result.failure()
return
}
}
response.headers().filter { it.first == HTTP_HEADER_LINK && it.second.contains(REL_NEXT) }.forEach {
val parts = it.second.split(OPEN_ANGLE, CLOSE_ANGLE)
if (parts.size >= 2) {
managePages(accessToken, service.myApiCall(accessToken, parts[1]))
}
}
}
private suspend fun managePage(response: Response<List<MyPage>>) {
val pages = response.body()
pages?.let {
persistResponse(it)
}
}
private suspend fun persistResponse(myPage: List<MyPage>) {
val myPageDOs = ArrayList<MyPageDO>()
myPage.forEach { page ->
myPageDOs.add(page.mapDO())
}
database.myPageDAO().insertAsync(myPageDOs)
}
My numerous issues are
This code does not insert all data items that I retrieve
How do complete the flow when all data items have been retrieved
How do I complete the GlobalScope job once all the data items have been retrieved and persisted
UPDATE
By making the following changes I have managed to insert all the data
private val persistenceThreadPool = Executors.newFixedThreadPool(3).asCoroutineDispatcher()
private val completed = CompletableDeferred<Int>()
private val channel = Channel<Response<List<MyPage>>?>(UNLIMITED)
private val channelFlow = channel.consumeAsFlow().flowOn(persistenceThreadPool)
private val frank: Job
init {
frank = GlobalScope.launch(persistenceThreadPool) {
channelFlow.collect { page ->
if (page == null) {
completed.complete(totalItems)
} else managePage(page!!)
}
}
}
...
...
...
channel.send(null)
completed.await()
return result ?: Result.success(outputData)
I do not like having to rely on a CompletableDeferred, is there a better approach than this to know when the Flow has completed everything?
You are looking for the flow builder and Flow.buffer():
suspend fun getData(): Flow<Data> = flow {
var pageData: List<Data>
var pageUrl: String? = "bla"
while (pageUrl != null) {
TODO("fetch pageData from pageUrl and change pageUrl to the next page")
emitAll(pageData)
}
}
.flowOn(Dispatchers.IO /* no need for a thread pool executor, IO does it automatically */)
.buffer(3)
You can use it just like a normal Flow, iterate, etc. If you want to know the total length of the output, you should calculate it on the consumer with a mutable closure variable. Note you shouldn't need to use GlobalScope anywhere (ideally ever).
There are a few ways to achieve the desired behaviour. I would suggest to use coroutineScope which is designed specifically for parallel decomposition. It also provides good cancellation and error handling behaviour out of the box. In conjunction with Channel.close behaviour it makes the implementation pretty simple. Conceptually the implementation may look like this:
suspend fun fetchAllPages() {
coroutineScope {
val channel = Channel<MyPage>(Channel.UNLIMITED)
launch(Dispatchers.IO){ loadData(channel) }
launch(Dispatchers.IO){ processData(channel) }
}
}
suspend fun loadData(sendChannel: SendChannel<MyPage>){
while(hasMoreData()){
sendChannel.send(loadPage())
}
sendChannel.close()
}
suspend fun processData(channel: ReceiveChannel<MyPage>){
for(page in channel){
// process page
}
}
It works in the following way:
coroutineScope suspends until all children are finished. So you don't need CompletableDeferred anymore.
loadData() loads pages in cycle and posts them into the channel. It closes the channel as soon as all pages have been loaded.
processData fetches items from the channel one by one and process them. The cycle will finish as soon as all the items have been processed (and the channel has been closed).
In this implementation the producer coroutine works independently, with no back-pressure, so it can take a lot of memory if the processing is slow. Limit the buffer capacity to have the producer coroutine suspend when the buffer is full.
It might be also a good idea to use channels fan-out behaviour to launch multiple processors to speed up the computation.
I am building a client application which uses Firebase for two things:
User Authentication
Using a realtime database
I have managed to set up everything correctly on my client and on my backend server (using Firebase's Admin SDK) and am able to correctly authenticate users and allow them to read/write to the database.
I am also using Retrofit2 to send requests from the client to the backend.
As part of allowing users access to the database, it is needed to send the user's token to the backend so the user can be verified.
To do this, I have the following logic:
val user = FirebaseAuth.getInstance().currentUser
if (user != null) {
user.getIdToken(false).addOnCompleteListener {
if (it.isSuccessful) {
val token = it.result?.token
//retrofit logic to send request happens from here
}
}
As you can see, getting the Id token of the user is an asynchronous call and in the current code base that I have, I have this code block for each one of my calls to the backend (duplication).
I want to know how I can export this snippet to a function (maybe a suspend method?) so that it can be reused for every call to the backend
I have searched online and have seen many SO questions, but none that fit this scenario.
I have thought about passing in a callback, but I have several methods that communicate to the backend, and each of them will require a different callback method.
The solution I am looking for looks something like this:
fun fetchDataFromDB() {
getIdTokenForUser()
//wait till it finishes and then
//perform request to DB
}
fun updateDataInDB() {
getIdTokenForUser()
//wait till it finishes and then
//perform request to DB
}
//......
I have tried reading about and implementing coroutines, but I lack the knowledge to do so correctly.
EDIT
Thanks to #Doug Stevenson for his answer and direction, I have managed to construct the following:
private suspend fun getUserIdToken(user: FirebaseUser) = coroutineScope {
val job = async {
user.getIdToken(false).result?.token
}
job.await()
}
And I use it in this fashion:
fun updateDB(context: Context) = runBlocking {
val user = FirebaseAuth.getInstance().currentUser
if (user != null) {
val token = getUserIdToken(user)
}
}
Is this the correct approach? Since the answers given below present a different implementation.
getIdToken is asynchronous returns a Task object. If you want to use a Task object in a Kotlin coroutine, you can use the library kotlinx-coroutines-play-services to add an extension method await() to the Task that makes it usable in a coroutine. With that, you can write something like this:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.3.9"
import kotlinx.coroutines.tasks.await
suspend fun getIdTokenForUser(user: FirebaseUser): GetTokenResult {
return try {
user.getIdToken(false).await()
}
catch (e: Exception) {
// handle error
}
}
You might have to update the types here - I didn't try to compile or test this.
See also:
Android kotlin task to be executed using coroutines
Coroutines And Firebase: How to Implement Javascript-like Promise.all()
Using Firebase with Kotlin coroutines
In order to go from a callback based API like the following one:
val myCallback = object : ServiceCallback() {
override fun onResult(theobject: Something) {
// your callback code here
}
override fun onFailure(ex: Throwable) {
// error handling
}
}
theService.enqueue(callback)
You can use suspendCoroutine
What it does is that it suspends execution until the continuation is satified by the callback. So you can write a KTX like the following:
suspend fun Service.getSomething(): Something = suspendCoroutine{ cont ->
val callback = object : ServiceCallback(){
override fun onSuccess(data: Something): Unit = cont.resume(data)
override fun onFailure(ex: Throwable): Unit = cont.resume(ex)
}
this.enqueue(callback)
}
I have my architecture like so:
Dao methods returning Flow<T>:
#Query("SELECT * FROM table WHERE id = :id")
fun itemById(id: Int): Flow<Item>
Repository layer returning items from DB but also backfilling from network:
(* Need help here -- this is not working as intended **)
fun items(): Flow<Item> = flow {
// Immediately emit values from DB
emitAll(itemDao.itemById(1))
// Backfill DB via network request without blocking coroutine
itemApi.makeRequest()
.also { insert(it) }
}
ViewModel layer taking the flow, applying any transformations, and converting it into a LiveData using .asLiveData():
fun observeItem(): LiveData<Item> = itemRepository.getItemFlow()
.map { // apply transformation to view model }
.asLiveData()
Fragment observing LiveData emissions and updating UI:
viewModel.item().observeNotNull(viewLifecycleOwner) {
renderUI(it)
}
The issue I'm having is at step 2. I can't seem to figure out a way to structure the logic so that I can emit the items from Flow immediately, but also perform the network fetch without waiting.
Since the fetch from network logic is in the same suspend function it'll wait for the network request to finish before emitting the results downstream. But I just want to fire that request independently since I'm not interested in waiting for a result (when it comes back, it'll update Room and I'll get the results naturally).
Any thoughts?
EDIT
Marko's solution works well for me, but I did attempt a similar approach like so:
suspend fun items(): Flow<List<Cryptocurrency>> = coroutineScope {
launch {
itemApi.makeRequest().also { insert(it) }
}
itemDao.itemById(1)
}
It sounds like you're describing a background task that you want to launch. For that you need access to your coroutine scope, so items() should be an extension function on CoroutineScope:
fun CoroutineScope.items(): Flow<Item> {
launch {
itemApi.makeRequest().also { insert(it) }
}
return flow {
emitAll(itemDao.itemById(1))
}
}
On the other hand, if you'd like to start a remote fetch whose result will also become a part of the response, you can do it as follows:
fun items(): Flow<Item> = flow {
coroutineScope {
val lateItem = async { itemApi.makeRequest().also { insert(it) } }
emitAll(itemDao.itemById(1))
emit(lateItem.await())
}
}