From 5ff0c5889fcf39725dabe38d4a7fcb1723d0cb82 Mon Sep 17 00:00:00 2001 From: Kirill Nayduik Date: Tue, 12 Apr 2022 15:56:24 +0300 Subject: [PATCH] Add implementation of blocking requests --- .../touchin/network/blocking/BlockingCall.kt | 54 +++++++++++++++++++ .../network/blocking/BlockingRequest.kt | 5 ++ .../blocking/BlockingRequestCallAdapter.kt | 27 ++++++++++ .../blocking/PendingRequestsManager.kt | 35 ++++++++++++ .../java/ru/touchin/network/utils/Request.kt | 6 +++ 5 files changed, 127 insertions(+) create mode 100644 network/src/main/java/ru/touchin/network/blocking/BlockingCall.kt create mode 100644 network/src/main/java/ru/touchin/network/blocking/BlockingRequest.kt create mode 100644 network/src/main/java/ru/touchin/network/blocking/BlockingRequestCallAdapter.kt create mode 100644 network/src/main/java/ru/touchin/network/blocking/PendingRequestsManager.kt create mode 100644 network/src/main/java/ru/touchin/network/utils/Request.kt diff --git a/network/src/main/java/ru/touchin/network/blocking/BlockingCall.kt b/network/src/main/java/ru/touchin/network/blocking/BlockingCall.kt new file mode 100644 index 0000000..7d2812a --- /dev/null +++ b/network/src/main/java/ru/touchin/network/blocking/BlockingCall.kt @@ -0,0 +1,54 @@ +package ru.touchin.network.blocking + +import okhttp3.Request +import okio.Timeout +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import ru.touchin.network.utils.getAnnotation + +class BlockingCall( + private val callDelegate: Call +) : Call { + override fun clone(): Call = callDelegate.clone() + + override fun execute(): Response { + return callDelegate.execute() + } + + override fun enqueue(callback: Callback) { + if (PendingRequestsManager.isPending.get()) { + PendingRequestsManager.addPendingRequest(callDelegate, callback) + return + } + + val isBlocking = callDelegate.isBlocking() + if (isBlocking) PendingRequestsManager.isPending.set(true) + + callDelegate.enqueue(object: Callback { + override fun onResponse(call: Call, response: Response) { + callback.onResponse(call, response) + + if (isBlocking) PendingRequestsManager.executePendingRequests() + } + + override fun onFailure(call: Call, t: Throwable) { + callback.onFailure(call, t) + + if (isBlocking) PendingRequestsManager.dropPendingRequests() + } + }) + } + + override fun isExecuted(): Boolean = callDelegate.isExecuted + + override fun cancel() = callDelegate.cancel() + + override fun isCanceled(): Boolean = callDelegate.isCanceled + + override fun request(): Request = callDelegate.request() + + override fun timeout(): Timeout = callDelegate.timeout() + + private fun Call.isBlocking() = request().getAnnotation(BlockingRequest::class.java) != null +} diff --git a/network/src/main/java/ru/touchin/network/blocking/BlockingRequest.kt b/network/src/main/java/ru/touchin/network/blocking/BlockingRequest.kt new file mode 100644 index 0000000..1cf3995 --- /dev/null +++ b/network/src/main/java/ru/touchin/network/blocking/BlockingRequest.kt @@ -0,0 +1,5 @@ +package ru.touchin.network.blocking + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FUNCTION) +annotation class BlockingRequest(val abortOnFail: Boolean = false) diff --git a/network/src/main/java/ru/touchin/network/blocking/BlockingRequestCallAdapter.kt b/network/src/main/java/ru/touchin/network/blocking/BlockingRequestCallAdapter.kt new file mode 100644 index 0000000..310fb56 --- /dev/null +++ b/network/src/main/java/ru/touchin/network/blocking/BlockingRequestCallAdapter.kt @@ -0,0 +1,27 @@ +package ru.touchin.network.blocking + +import retrofit2.Call +import retrofit2.CallAdapter +import retrofit2.Retrofit +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type + +class BlockingRequestCallAdapter private constructor( + private val responseType: Type +) : CallAdapter { + + companion object { + fun create() = object : CallAdapter.Factory() { + override fun get(returnType: Type, annotations: Array, retrofit: Retrofit): CallAdapter<*, *>? { + return (returnType as? ParameterizedType) + ?.let { BlockingRequestCallAdapter(responseType = it.actualTypeArguments[0]) } + } + } + } + + override fun responseType(): Type = responseType + + override fun adapt(call: Call): Any { + return BlockingCall(call) + } +} diff --git a/network/src/main/java/ru/touchin/network/blocking/PendingRequestsManager.kt b/network/src/main/java/ru/touchin/network/blocking/PendingRequestsManager.kt new file mode 100644 index 0000000..a82c238 --- /dev/null +++ b/network/src/main/java/ru/touchin/network/blocking/PendingRequestsManager.kt @@ -0,0 +1,35 @@ +package ru.touchin.network.blocking + +import retrofit2.Call +import retrofit2.Callback +import java.util.concurrent.atomic.AtomicBoolean + +object PendingRequestsManager { + + val isPending = AtomicBoolean(false) + + private val pendingRequests = mutableListOf, Callback>>() + + fun addPendingRequest(call: Call, callback: Callback) { + pendingRequests.add(call to callback) + } + + @Synchronized + fun executePendingRequests() { + applyActionToPendingRequests { first.enqueue(second) } + } + + @Synchronized + fun dropPendingRequests() { + applyActionToPendingRequests { first.cancel() } + } + + private fun applyActionToPendingRequests(action: Pair, Callback>.() -> Unit) { + isPending.set(false) + + pendingRequests.forEach { it.action() } + + pendingRequests.clear() + } + +} diff --git a/network/src/main/java/ru/touchin/network/utils/Request.kt b/network/src/main/java/ru/touchin/network/utils/Request.kt new file mode 100644 index 0000000..842081c --- /dev/null +++ b/network/src/main/java/ru/touchin/network/utils/Request.kt @@ -0,0 +1,6 @@ +package ru.touchin.network.utils + +import okhttp3.Request +import retrofit2.Invocation + +fun Request.getAnnotation(annotation: Class) = tag(Invocation::class.java)?.method()?.getAnnotation(annotation)