Last active
May 9, 2020 07:33
-
-
Save roschlau/246e9ae7f2f8e4abb0576e4d8415c858 to your computer and use it in GitHub Desktop.
Kotlin Coroutines implementation of "More Granular Retry" from https://www.techyourchance.com/concurrency-frameworks-overrated-android/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@multlurk
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.