-
-
Save roschlau/246e9ae7f2f8e4abb0576e4d8415c858 to your computer and use it in GitHub Desktop.
| import kotlinx.coroutines.Dispatchers | |
| import kotlinx.coroutines.async | |
| import kotlinx.coroutines.suspendCancellableCoroutine | |
| import kotlinx.coroutines.withContext | |
| import java.io.File | |
| class UploadFilesUseCase : BaseBusyObservable<UploadFilesUseCase.Listener>() { | |
| interface Listener { | |
| fun onFilesUploaded() | |
| fun onFilesUploadFailed() | |
| } | |
| companion object { | |
| private const val MAX_RETRIES = 3 | |
| } | |
| suspend fun uploadFiles() { | |
| if (!isFreeAndBecomeBusy()) { | |
| // log concurrent invocation attempt | |
| return | |
| } | |
| uploadFilesSync() | |
| } | |
| private suspend fun uploadFilesSync() = withContext(Dispatchers.IO) { | |
| val aJob = async { processAndMergeFilesOfTypeAWithRetry() } | |
| val bJob = async { processAndMergeFilesOfTypeBWithRetry() } | |
| val mergedA = aJob.await() | |
| val mergedB = bJob.await() | |
| if (mergedA == null || mergedB == null) { | |
| flowFailed() | |
| return@withContext | |
| } | |
| val archive: File = try { | |
| compressMergedFiles(mergedA, mergedB) | |
| } catch (e: OperationFailedException) { | |
| flowFailed() | |
| throw e | |
| } | |
| if (uploadFileToServerWithRetry(archive)) { | |
| deleteTempDir() | |
| notifySuccess() | |
| } else { | |
| flowFailed() | |
| } | |
| } | |
| private suspend fun uploadFileToServerWithRetry(archive: File): Boolean { | |
| for (i in 0 until MAX_RETRIES) { | |
| val responseCode = uploadFileToServer(archive) | |
| if (responseCode / 100 == 2) { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| private suspend fun uploadFileToServer(archive: File): Int { | |
| return suspendCancellableCoroutine { continuation -> | |
| HttpManager.getInstance.uploadFiles( | |
| archive, | |
| object : HttpRequestListener() { | |
| fun onDone(code: Int, body: ByteArray?) { | |
| continuation.resumeWith(Result.success(code)) | |
| } | |
| fun onFailure() { | |
| continuation.resumeWith(Result.success(0)) | |
| } | |
| } | |
| ) | |
| } | |
| } | |
| private suspend fun flowFailed() { | |
| deleteTempDir() | |
| notifyFailure() | |
| } | |
| private fun processAndMergeFilesOfTypeAWithRetry(): File? { | |
| for (i in 0 until MAX_RETRIES) { | |
| try { | |
| // ... | |
| } catch (e: OperationFailedException) { | |
| // log the exception | |
| } | |
| } | |
| return null | |
| } | |
| private fun processAndMergeFilesOfTypeBWithRetry(): File? { | |
| for (i in 0 until MAX_RETRIES) { | |
| try { | |
| // ... | |
| } catch (e: OperationFailedException) { | |
| // log the exception | |
| } | |
| } | |
| return null | |
| } | |
| @Throws(OperationFailedException::class) | |
| private fun compressMergedFiles(fileA: File?, fileB: File?): File { TODO() } | |
| private fun deleteTempDir() { TODO() } | |
| private suspend fun notifySuccess() = withContext(Dispatchers.Main) { | |
| for (listener in listeners) { | |
| listener.onFilesUploaded() | |
| } | |
| becomeNotBusy() | |
| } | |
| private suspend fun notifyFailure() = withContext(Dispatchers.Main) { | |
| for (listener in listeners) { | |
| listener.onFilesUploadFailed() | |
| } | |
| becomeNotBusy() | |
| } | |
| } |
@techyourchance It's been quite some time since I actually worked on Android and have thus never worked with ViewModel, so I have no idea how that would integrate specifically 😅 So yeah, points taken, Making the coroutines an implementation detail certainly makes sense in this kind of scenario. I'm used to working in a backend service that's coroutines through and through, so I didn't really think about this.
About awaitAll vs individual awaits: Mostly because I wanted to leave the function signatures mostly intact, and having the async jobs in a list would have required to change the compressMergedFiles call, or work around it awkwardly. So, I just left it that way. But yeah, in a real scenario I would probably use that as well.
How to get rid of BaseBusyObservable's uploadFilesSync and becomeNotBusy?
I don't know the implementation details and requirements of the base class, so I just left these as they are. Would probably need to use a Mutex under the hood to play nice with coroutines.
How to get rid of BaseBusyObservable's uploadFilesSync and becomeNotBusy?