Queue multiple async calls with callbacks - android

I need to fetch some images from the gallery, process them (resize, compress...) and save them to a certain path. However, i need to queue the calls because older devices won't be able to process multiple images at the same time.
I am using Glide, this is the code used for processing one image:
fun processImage(context: Context, sourcePath: String, destinationPath: String, quality: Int, width: Int, height: Int, deleteOriginal: Boolean, callback: ((success: Boolean) -> Unit)) {
val sourceFile = File(sourcePath)
val destinationFile = File(destinationPath)
GlideApp.with(context)
.asBitmap()
.load(sourceFile)
.into(object : SimpleTarget<Bitmap>(width, height) {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
try {
destinationFile.writeBytes(ImageUtilities.imageToByteArray(resource, quality, Bitmap.CompressFormat.JPEG, false))
if (deleteOriginal) {
val originalFile = File(sourcePath)
originalFile.delete()
}
callback.invoke(true)
} catch (ex: Exception) {
callback.invoke(false)
}
}
})
}
Now i am queuing the calls manually by calling processNextImage which calls itself recursively until all the images are processed:
private fun processImages(sourceImagePaths: List<String>) {
processNextImage(sourceImagePaths, 0)
}
private fun processNextImage(sourceImagePaths: List<String>, index: Int) {
val imagePath = sourceImagePaths[index]
val destination = FileUtilities.generateImagePath()
processImage(this, imagePath, destination, 90, 1000, 1000, false) {
processedImagePaths.add(destination)
if (index + 1 < sourceImagePaths.count())
processImage(sourceImagePaths, index + 1)
else
success()
}
}
However I don't think this is the best way to do it and I tried to look into Kotlin coroutines but all I found were examples when the queued code is already blocking, which doesn't fit my case because Glide already handles the resizing asynchronously and returns the result in a callback onResourceReady
Any ideas for a clean way to do this?

As described in the official documentation, there is a simple pattern to follow if you want to turn a callback-based API into one based on suspendable functions. I'll paraphrase that description here.
Your key tool is the function from the standard library called suspendCoroutine(). Assume that you have someLongComputation function with a callback that receives a Result object:
fun someLongComputation(params: Params, callback: (Result) -> Unit)
You can convert it into a suspending function with the following straightforward code:
suspend fun someLongComputation(params: Params): Result =
suspendCoroutine { cont ->
someLongComputation(params) { cont.resume(it) }
}
Note how the type of the object passed to the original callback became simply the return value of the suspendable function.
With this you can see the magic of coroutines happen right in front of you: even though it looks exactly like a blocking call, it isn't. The coroutine will get suspended behind the scenes and resume when the return value is ready — and how it will resume is totally under your control.

I was able to solve the issue using suspendCoroutine as suggested in Marko's comment, here is my code:
private fun processImages(sourceImagePaths: List<String>) {
async(UI) {
sourceImagePaths.forEach { path ->
processNextImage(path)?.let {
processedImagePaths.add(it)
}
}
if (processedImagePaths.isEmpty()) finishWithFailure() else finishWithSuccess()
}
}
private suspend fun processNextImage(sourceImagePath: String): String? = suspendCoroutine { cont ->
val destination = FileUtilities.generateImagePath()
processImage(this, sourceImagePath, destination, 90, 1000, 1000, false) { success ->
if (success)
cont.resume(destination)
else
cont.resume(null)
}
}
The method processImages iterates over the list of paths, and calls processNextImage for each path. Since processNextImage contains a suspendCoroutine, it will block the thread until cont.resume is called, which guarantees that the next image will not be processed before the current one is done.

Related

Kotlin, wait callback one by one

I have method, which returns response from server. For example:
fun uploadVideo(link: String, completionHandler: (Result<String>) -> Unit) {
// some action
completionHandler(Result.success(""))
}
I want to call this method one by one. Wait for a response from the previous one to call the next one. For example
uploadVideo("https://stackoverflow.com/video1.mp4") {
}
// call this only when i have response from preview request
uploadVideo("https://stackoverflow.com/video2.mp4") {
}
// call this only when i have response from preview request
uploadVideo("https://stackoverflow.com/video3.mp4") {
}
I tried use suspendCancellableCoroutine, like this
suspend fun uploadVideo(link: String?): String? = suspendCancellableCoroutine { cont ->
uri?.let {
uploadVideo(link,
completionHandler = {
it.onSuccess { uri ->
cont.resume(uri.toString())
}.onFailure {
cont.resumeWithException(it)
}
}
)
} ?: kotlin.run {
cont.resume(null)
}
}
and then call like this:
uploadVideo("https://stackoverflow.com/video1.mp4")
uploadVideo("https://stackoverflow.com/video2.mp4")
uploadVideo("https://stackoverflow.com/video3.mp4")
but these methods are not called sequentially, but in parallel
Note, the contents of your example API function don't quite make sense. If the callback were simply called inside the body of the function, then that would mean the function was blocking the whole time, which would mean there would be no reason for it to even have a callback. It could just directly return the value.
The actual contents of the API function might look more like this:
fun uploadVideo(link: String, completionHandler: (Result<String>) -> Unit) {
val callbackHandler = Handler(Looper.myLooper())
someOtherHandlerOrThreadPool.run {
// some action
callbackHandler.post {
completionHandler(Result.success(""))
}
}
}
The reason I bring that up is that the alternative to nesting a bunch of callbacks is use suspend functions and coroutines, but the code to convert the above to a suspend function doesn't make sense if it were a blocking function like in your version of it.
The basic pattern to convert a callback-based function into a suspend function is to use suspendCoroutine or suspendCancellableCoroutine. If uploadVideo was a function in some api class, you can define it as an extension function:
suspend fun SomeApiClass.uploadVideo(link: String): Result<String> = withContext(Dispatchers.Main) {
suspendCoroutine { cont ->
uploadVideo(link) { cont.resume(it) }
}
}
Now you can call this suspend function repeatedly in sequence if you're inside a coroutine or another suspend function:
fun foo() {
viewModelScope.launch {
val result1 = uploadVideo("https://stackoverflow.com/video1.mp4")
val result2 = uploadVideo("https://stackoverflow.com/video2.mp4")
val result3 = uploadVideo("https://stackoverflow.com/video3.mp4")
}
}
You could try this. this waits till the previous method called its callback and then runs the next one. Only if you have many images this is not a really nice way to do this.
fun uploadVideo(link: String, completionHandler: () -> Unit) {
// some action
completionHandler()
}
uploadVideo("https://stackoverflow.com/video1.mp4") {
uploadVideo("https://stackoverflow.com/video2.mp4") {
uploadVideo("https://stackoverflow.com/video3.mp4") {}
}
}

How to cancel a combine of flows when one of them emits a certain value?

I am doing multiple network requests in parallel and monitoring the result using a Stateflow.
Each network request is done in a separate flow, and I use combine to push the latest status on my Stateflow. Here's my code:
Repo class:
fun networkRequest1(id: Int): Flow<Resource<List<Area>>> =
flow {
emit(Resource.Loading())
try {
val areas = retrofitInterface.getAreas(id)
emit(Resource.Success(areas))
} catch (throwable: Throwable) {
emit(
Resource.Error()
)
)
}
}
fun networkRequest2(id: Int): Flow<Resource<List<Area>>> = //same code as above for simplicity
fun networkRequest3(id: Int): Flow<Resource<List<Area>>> = //same code as above for simplicity
fun networkRequest4(id: Int): Flow<Resource<List<Area>>> = //same code as above for simplicity
ViewModel class:
val getDataCombinedStateFlow: StateFlow<Resource<HashMap<String, Resource<out List<Any>>>>?> =
getDataTrigger.flatMapLatest {
withContext(it) {
combine(
repo.networkRequest1(id: Int),
repo.networkRequest2(id: Int),
repo.networkRequest3(id: Int),
repo.networkRequest4(id: Int)
) { a,
b,
c,
d
->
hashMapOf(
Pair("1", a),
Pair("2",b),
Pair("3", c),
Pair("4", d),
)
}.flatMapLatest {
val progress = it
var isLoading = false
flow<Resource<HashMap<String, Resource<out List<Any>>>>?> {
emit(Resource.Loading())
progress.forEach { (t, u) ->
if (u is Resource.Error) {
emit(Resource.Error(error = u.error!!))
// I want to cancel here, as I no longer care if 1 request fails
return#flow
}
if (u is Resource.Loading) {
isLoading = true
}
}
if (isLoading) {
emit(Resource.Loading())
return#flow
}
if (!isLoading) {
emit(Resource.Success(progress))
}
}
}
}
}.stateIn(viewModelScope, SharingStarted.Lazily, null)
View class:
viewLifecycleOwner.lifecycleScope.launchWhenCreated() {
viewModel.getDataCombinedStateFlow.collect {
val result = it ?: return#collect
binding.loadingErrorState.apply {
if (result is Resource.Loading) {
//show smth
}
if (result is Resource.Error) {
//show error msg
}
if (result is Resource.Success) {
//done
}
}
}
}
I want to be able to cancel all work after a Resource.Error is emitted, as I no longer want to wait or do any related work for the response of other API calls in case one of them fails.
How can I achieve that?
I tried to cancel the collect, but the flows that build the Stateflow keep working and emmit results. I know that they won't be collected but still, I find this a waste of resources.
I think this whole situation is complicated by the fact that you have source flows just to precede what would otherwise be suspend functions with a Loading state. So then you're having to merge them and filter out various loading states, and your end result flow keeps repeatedly emitting a loading state until all the sources are ready.
If you instead have basic suspend functions for your network operations, for example:
suspend fun networkRequest1(id: Int): List<Area> =
retrofitInterface.getAreas(id)
Then your view model flow becomes simpler. It doesn't make sense to use a specific context just to call a flow builder function, so I left that part out. (I'm also confused as to why you have a flow of CoroutineContexts.)
I also think it's much cleaner if you break out the request call into a separate function.
private fun makeParallelRequests(id: Int): Map<String, Resource<out List<Any>> = coroutineScope {
val results = listOf(
async { networkRequest1(id) },
async { networkRequest2(id) },
async { networkRequest2(id) },
async { networkRequest4(id) }
).awaitAll()
.map { Resource.Success(it) }
listOf("1", "2", "3", "4").zip(results).toMap()
}
val dataCombinedStateFlow: StateFlow<Resource<Map<String, Resource<out List<Any>>>>?> =
getDataTrigger.flatMapLatest {
flow {
emit(Resource.Loading())
try {
val result = makeParallelRequests(id)
emit(Resource.Success(result))
catch (e: Throwable) {
emit(Resource.Error(e))
}
}
}
I agree with #Tenfour04 that those nested flows are overly complicated and there are several ways to simplify this (#Tenfour04's solution is a good one).
If you don't want to rewrite everything then you can fix that one line that breaks the structured concurrency:
.stateIn(viewModelScope, SharingStarted.Lazily, null)
With this the whole ViewModel flow is started in the ViewModel's scope while the view starts the collect from a separate scope (viewLifecycleOwner.lifecycleScope which would be the Fragment / Activity scope).
If you want to cancel the flow from the view, you need to use either the same scope or expose a cancel function that would cancel the ViewModel's scope.
If you want to cancel the flow from the ViewModel itself (at the return#flow statement) then you can simply add:
viewModelScope.cancel()

Kotlin Coroutine Flow: When does wasting resource happen when using Flow

I am reading this article to fully understand the dos and donts of using Flow while comparing it to my implementation, but I can't grasp clearly how to tell if you are wasting resource when using Flow or flow builder. When is the time a flow is being release/freed in memory and when is the time that you are wasting resource like accidentally creating multiple instances of flow and not releasing them?
I have a UseCase class that invokes a repository function that returns Flow. In my ViewModel this is how it looks like.
class AssetViewModel constructor(private val getAssetsUseCase: GetAssetsUseCase) : BaseViewModel() {
private var job: Job? = null
private val _assetState = defaultMutableSharedFlow<AssetState>()
fun getAssetState() = _assetState.asSharedFlow()
init {
job = viewModelScope.launch {
while(true) {
if (lifecycleState == LifeCycleState.ON_START || lifecycleState == LifeCycleState.ON_RESUME)
fetchAssets()
delay(10_000)
}
}
}
fun fetchAssets() {
viewModelScope.launch {
withContext(Dispatchers.IO) {
getAssetsUseCase(
AppConfigs.ASSET_BASE_URL,
AppConfigs.ASSET_PARAMS,
AppConfigs.ASSET_SIZES[AppConfigs.ASSET_LIMIT_INDEX]
).onEach {
when(it){
is RequestStatus.Loading -> {
_assetState.tryEmit(AssetState.FetchLoading)
}
is RequestStatus.Success -> {
_assetState.tryEmit(AssetState.FetchSuccess(it.data.assetDataDomain))
}
is RequestStatus.Failed -> {
_assetState.tryEmit(AssetState.FetchFailed(it.message))
}
}
}.collect()
}
}
}
override fun onCleared() {
job?.cancel()
super.onCleared()
}
}
The idea here is we are fetching data from remote every 10 seconds while also allowing on demand fetch of data via UI.
Just a typical useless UseCase class
class GetAssetsUseCase #Inject constructor(
private val repository: AssetsRepository // Passing interface not implementation for fake test
) {
operator fun invoke(baseUrl: String, query: String, limit: String): Flow<RequestStatus<AssetDomain>> {
return repository.fetchAssets(baseUrl, query, limit)
}
}
The concrete implementation of repository
class AssetsRepositoryImpl constructor(
private val service: CryptoService,
private val mapper: AssetDtoMapper
) : AssetsRepository {
override fun fetchAssets(
baseUrl: String,
query: String,
limit: String
) = flow {
try {
emit(RequestStatus.Loading())
val domainModel = mapper.mapToDomainModel(
service.getAssetItems(
baseUrl,
query,
limit
)
)
emit(RequestStatus.Success(domainModel))
} catch (e: HttpException) {
emit(RequestStatus.Failed(e))
} catch (e: IOException) {
emit(RequestStatus.Failed(e))
}
}
}
After reading this article which says that using stateIn or sharedIn will improve the performance when using a flow, it seems that I am creating new instances of the same flow on-demand. But there is a limitation as the stated approach only works for variable and not function that returns Flow.
stateIn and shareIn can save resources if there are multiple observers, by avoiding redundant fetching. And in your case, you could set it up to automatically pause the automatic re-fetching when there are no observers. If, on the UI side you use repeatOnLifecycle, then it will automatically drop your observers when the view is off screen and then you will avoid wasted fetches the user will never see.
I think it’s not often described this way, but often the multiple observers are just observers coming from the same Activity or Fragment class after screen rotations or rapidly switching between fragments. If you use WhileSubscribed with a timeout to account for this, you can avoid having to restart your flow if it’s needed again quickly.
Currently you emit to from an external coroutine instead of using shareIn, so there’s no opportunity to pause execution.
I haven't tried to create something that supports both automatic and manual refetching. Here's a possible strategy, but I haven't tested it.
private val refreshRequest = Channel<Unit>(Channel.CONFLATED)
fun fetchAssets() {
refreshRequest.trySend(Unit)
}
val assetState = flow {
while(true) {
getAssetsUseCase(
AppConfigs.ASSET_BASE_URL,
AppConfigs.ASSET_PARAMS,
AppConfigs.ASSET_SIZES[AppConfigs.ASSET_LIMIT_INDEX]
).map {
when(it){
is RequestStatus.Loading -> AssetState.FetchLoading
is RequestStatus.Success -> AssetState.FetchSuccess(it.data.assetDataDomain)
is RequestStatus.Failed -> AssetState.FetchFailed(it.message)
}
}.emitAll()
withTimeoutOrNull(100L) {
// drop any immediate or pending manual request
refreshRequest.receive()
}
// Wait until a fetch is manually requested or ten seconds pass:
withTimeoutOrNull(10000L - 100L) {
refreshRequest.receive()
}
}
}.shareIn(viewModelScope, SharingStarted.WhileSubscribed(4000L), replay = 1)
To this I would recommend not using flow as the return type of the usecase function and the api call must not be wrapped inside a flow builder.
Why:
The api call actually is happening once and then again after an interval it is triggered by the view model itself, returning flow from the api caller function will be a bad usage of powerful tool that is actually meant to be called once and then it must be self-reliant, it should emit or pump in the data till the moment it has a subscriber/collector.
One usecase you can consider when using flow as return type from the room db query call, it is called only once and then the room emits data into it till the time it has subscriber.
.....
fun fetchAssets() {
viewModelScope.launch {
// loading true
val result=getusecase(.....)
when(result){..process result and emit on state..}
// loading false
}
}
.....
suspend operator fun invoke(....):RequestStatus<AssetDomain>{
repository.fetchAssets(baseUrl, query, limit)
}
.....
override fun fetchAssets(
baseUrl: String,
query: String,
limit: String
):RequestStatus {
try {
//RequestStatus.Loading()//this can be managed in viewmodel itself
val domainModel = mapper.mapToDomainModel(
service.getAssetItems(
baseUrl,
query,
limit
)
)
RequestStatus.Success(domainModel)
} catch (e: HttpException) {
RequestStatus.Failed(e)
} catch (e: IOException) {
RequestStatus.Failed(e)
}
}

Can we download multiple files in a Single Coroutine in Android Kotlin

Hi all Developers pls sort out my query as much as possible.
I'm working on Image Album application. Here while user click on particular album we should download all related images to that particular album.
For example album name is Tree so that album have multiple image url's array. So i should download all images from that album by array of url's
Ex : imageArray = arrayOf("url1","url2","url3","url4",....etc url(n))
i should put them in for loop or else recursion then i should download them upto (n) urls on completion of one by one.
i have written snippet for one file download here my doubt is how i can proceed to download multiple files.
should i use same coroutine for all files download or else one one coroutine for one one file
CoroutineScope(Dispatchers.IO).launch {
**//here itself i can run for loop or else any other robust/proper way to do this requirement.**
ktor.downloadFile(outputStream, url).collect {
withContext(Dispatchers.Main) {
when (it) {
is DownloadResult.Success -> {
**//on success of one file download should i call recursively to download one more file by this method - private fun downloadFile(context: Context, url: String, file: Uri)**
viewFile(file)
}
below is the code to download a single file
private fun downloadFile(context: Context, url: String, file: Uri) {
val ktor = HttpClient(Android)
contentResolver.openOutputStream(file)?.let { outputStream ->
CoroutineScope(Dispatchers.IO).launch {
ktor.downloadFile(outputStream, url).collect {
withContext(Dispatchers.Main) {
when (it) {
is DownloadResult.Success -> {
viewFile(file)
}
is DownloadResult.Error -> {
}
is DownloadResult.Progress -> {
txtProgress.text = "${it.progress}"
}
}
}
}
}
}
}
suspend fun HttpClient.downloadFile(file: OutputStream, url: String): Flow<DownloadResult> {
return flow {
try {
val response = call {
url(url)
method = HttpMethod.Get
}.response
val data = ByteArray(response.contentLength()!!.toInt())
var offset = 0
do {
val currentRead = response.content.readAvailable(data, offset, data.size)
offset += currentRead
val progress = (offset * 100f / data.size).roundToInt()
emit(DownloadResult.Progress(progress))
} while (currentRead > 0)
response.close()
if (response.status.isSuccess()) {
withContext(Dispatchers.IO) {
file.write(data)
}
emit(DownloadResult.Success)
} else {
emit(DownloadResult.Error("File not downloaded"))
}
} catch (e: TimeoutCancellationException) {
emit(DownloadResult.Error("Connection timed out", e))
} catch (t: Throwable) {
emit(DownloadResult.Error("Failed to connect"))
}
}
}
sealed class DownloadResult {
object Success : DownloadResult()
data class Error(val message: String, val cause: Exception? = null) : DownloadResult()
data class Progress(val progress: Int): DownloadResult()
}
Gradle Files i have used
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
implementation "io.ktor:ktor-client-android:1.2.5"
Should be possible.
Create another function that will iterate through the list of files to be downloaded.
With the new function, use an async or launch coroutine function - this allows for more control over the flow of your logic, sequential behavior or asynchronous behavior respectively.
e.g. fun downloadBatch(list: List<Uri>) { GlobalScope.async(Dispatchers.IO) { //logic goes here }}
Note: GlobalScope is just for an easy example - not advisable to use in live production.
Inside the iterator/for-loop, call a function to download an individual file. This particular function should be appended with suspend at the beginning
e.g. suspend fun downloadFile(uri: Uri)
Note: the suspended function won't use any threading logic itself and depends on being nested within a functional Coroutine.
Continue to use rxJava, or try LiveData, to broadcast your files.

Android download multiple files with OkHttp and coroutine

In my app, I get a set of urls to some images from an api and need to create Bitmap objects out of those urls to be able do display the images in the UI. I saw that the android docs recommend using corutines for performing such async tasks, but I am not sure how to do it properly.
Using OkHttp for my http client, I tried the following approach:
GlobalScope.launch {
val gson = Gson();
val parsedRes = gson.fromJson(
response.body?.charStream(),
Array<GoodreadsBook>::class.java
);
// Create the bitmap from the imageUrl
for (i in 0 until parsedRes.size) {
val bitmap =
GlobalScope.async { createBitmapFromUrl(parsedRes[i].best_book.image_url) }
parsedRes[i].best_book.imageBitmap = bitmap.await();
}
searchResults.postValue(parsedRes)
}
Where response is what I get back from my API, and searchResults is a LiveData that hold the parsed response.
Also, here is how I am getting the images from those urls:
suspend fun createBitmapFromUrl(url: String): Bitmap? {
val client = OkHttpClient();
val req = Request.Builder().url(url).build();
val res = client.newCall(req).execute();
return BitmapFactory.decodeStream(res.body?.byteStream())
}
Even though every fetch action is done on a separate coroutine, it's still too slow. Is there a better way of doing it? I can use any other http client if there is one out there optimized for use with coroutines, although I am new to Kotlin so I don't know any.
First of all the createBitmapFromUrl(url: String) does everything synchronously, you've to first stop them from blocking the coroutine thread, you may want to use Dispatchers.IO for that because callback isn't the most idomatic thing ever in coroutines.
val client = OkHttpClient() // preinitialize the client
suspend fun createBitmapFromUrl(url: String): Bitmap? = withContext(Dispatchers.IO) {
val req = Request.Builder().url(url).build()
val res = client.newCall(req).execute()
BitmapFactory.decodeStream(res.body?.byteStream())
}
Now, when you are calling bitmap.await() you are simply saying that "Hey, wait for the deferred bitmap and once it is finished resume the loop for next iteration"
So you may want to do the assignment in the coroutine itself to stop it from suspending the loop, otherwise create another loop for that. I'd go for first option.
scope.launch {
val gson = Gson();
val parsedRes = gson.fromJson(
response.body?.charStream(),
Array<GoodreadsBook>::class.java
);
// Create the bitmap from the imageUrl
for (i in 0 until parsedRes.size) {
launch {
parsedRes[i].best_book.imageBitmap = createBitmapFromUrl(parsedRes[i].best_book.image_url)
}
}
}
Use a library like the following that doesn't use the blocking execute method and instead bridges from the async enqueue.
https://github.com/gildor/kotlin-coroutines-okhttp
suspend fun main() {
// Do call and await() for result from any suspend function
val result = client.newCall(request).await()
println("${result.code()}: ${result.message()}")
}
What this basically does is the following
public suspend fun Call.await(): Response {
return suspendCancellableCoroutine { continuation ->
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
continuation.resume(response)
}
override fun onFailure(call: Call, e: IOException) {
if (continuation.isCancelled) return
continuation.resumeWithException(e)
}
})
continuation.invokeOnCancellation {
try {
cancel()
} catch (ex: Throwable) {
//Ignore cancel exception
}
}
}
}

Categories

Resources