-
-
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() | |
} | |
} |
How to get rid of BaseBusyObservable's uploadFilesSync and 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.
Also why not awaitAll instead of two individual awaits?