Loading app/src/main/java/foundation/e/apps/data/NetworkHandler.kt +22 −0 Original line number Diff line number Diff line Loading @@ -27,9 +27,11 @@ import foundation.e.apps.data.playstore.utils.GplayHttpRequestException import kotlinx.coroutines.CancellationException import kotlinx.coroutines.delay import timber.log.Timber import java.io.InterruptedIOException import java.net.SocketTimeoutException private const val TIMEOUT = "Timeout" private val TIMEOUT_MESSAGE_PATTERNS = listOf("timeout", "timed out") private const val UNKNOWN = "Unknown" private const val STATUS = "Status:" private const val ERROR_GPLAY_API = "Gplay api has faced error!" Loading @@ -49,6 +51,8 @@ suspend fun <T> handleNetworkResult(call: suspend () -> T): ResultSupreme<T> { throw e } catch (e: SocketTimeoutException) { handleSocketTimeoutException(e) } catch (e: InterruptedIOException) { handleInterruptedIOException(e) } catch (e: GplayHttpRequestException) { resultSupremeGplayHttpRequestException(e) } catch (e: Exception) { Loading @@ -63,6 +67,16 @@ private fun <T> handleSocketTimeoutException(e: SocketTimeoutException): ResultS return resultTimeout } private fun <T> handleInterruptedIOException(e: InterruptedIOException): ResultSupreme<T> { return if (isTimeoutInterruptedIOException(e)) { val resultTimeout = ResultSupreme.Timeout<T>(exception = e) resultTimeout.message = extractErrorMessage(e) resultTimeout } else { handleOthersException(e) } } private fun <T> resultSupremeGplayHttpRequestException(e: GplayHttpRequestException): ResultSupreme<T> { val message = extractErrorMessage(e) val exception = GPlayException(e.status == GPlayHttpClient.STATUS_CODE_TIMEOUT, message) Loading @@ -83,12 +97,20 @@ private fun extractErrorMessage(e: Exception): String { val status = when (e) { is GplayHttpRequestException -> e.status.toString() is SocketTimeoutException -> TIMEOUT is InterruptedIOException -> if (isTimeoutInterruptedIOException(e)) TIMEOUT else UNKNOWN else -> UNKNOWN } return (e.localizedMessage?.ifBlank { ERROR_GPLAY_API } ?: ERROR_GPLAY_API) + " $STATUS $status" } private fun isTimeoutInterruptedIOException(e: InterruptedIOException): Boolean { val message = e.message ?: return true return TIMEOUT_MESSAGE_PATTERNS.any { pattern -> message.contains(pattern, ignoreCase = true) } } suspend fun <T> retryWithBackoff(retryDelayInSeconds: Int = -1, operation: suspend () -> T): T? { var result: T? = null try { Loading app/src/test/java/foundation/e/apps/data/NetworkHandlerTest.kt +34 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ import foundation.e.apps.data.playstore.utils.GplayHttpRequestException import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test import java.io.InterruptedIOException import java.net.SocketTimeoutException @OptIn(ExperimentalCoroutinesApi::class) Loading @@ -33,6 +34,39 @@ class NetworkHandlerTest { assertThat(result.exception).isSameInstanceAs(timeout) } @Test fun handleNetworkResult_convertsInterruptedTimeout() = runTest { val timeout = InterruptedIOException("timeout") val result = handleNetworkResult<String> { throw timeout } assertThat(result.isTimeout()).isTrue() assertThat(result.message).isEqualTo("timeout Status: Timeout") assertThat(result.exception).isSameInstanceAs(timeout) } @Test fun handleNetworkResult_convertsInterruptedTimedOutMessage() = runTest { val timeout = InterruptedIOException("Read timed out") val result = handleNetworkResult<String> { throw timeout } assertThat(result.isTimeout()).isTrue() assertThat(result.message).isEqualTo("Read timed out Status: Timeout") assertThat(result.exception).isSameInstanceAs(timeout) } @Test fun handleNetworkResult_mapsNonTimeoutInterruptedIoExceptionToError() = runTest { val interrupted = InterruptedIOException("stream closed") val result = handleNetworkResult<String> { throw interrupted } assertThat(result.isUnknownError()).isTrue() assertThat(result.message).isEqualTo("stream closed Status: Unknown") assertThat(result.exception).isSameInstanceAs(interrupted) } @Test fun handleNetworkResult_mapsHttp429ToError() = runTest { val exception = GplayHttpRequestException( Loading Loading
app/src/main/java/foundation/e/apps/data/NetworkHandler.kt +22 −0 Original line number Diff line number Diff line Loading @@ -27,9 +27,11 @@ import foundation.e.apps.data.playstore.utils.GplayHttpRequestException import kotlinx.coroutines.CancellationException import kotlinx.coroutines.delay import timber.log.Timber import java.io.InterruptedIOException import java.net.SocketTimeoutException private const val TIMEOUT = "Timeout" private val TIMEOUT_MESSAGE_PATTERNS = listOf("timeout", "timed out") private const val UNKNOWN = "Unknown" private const val STATUS = "Status:" private const val ERROR_GPLAY_API = "Gplay api has faced error!" Loading @@ -49,6 +51,8 @@ suspend fun <T> handleNetworkResult(call: suspend () -> T): ResultSupreme<T> { throw e } catch (e: SocketTimeoutException) { handleSocketTimeoutException(e) } catch (e: InterruptedIOException) { handleInterruptedIOException(e) } catch (e: GplayHttpRequestException) { resultSupremeGplayHttpRequestException(e) } catch (e: Exception) { Loading @@ -63,6 +67,16 @@ private fun <T> handleSocketTimeoutException(e: SocketTimeoutException): ResultS return resultTimeout } private fun <T> handleInterruptedIOException(e: InterruptedIOException): ResultSupreme<T> { return if (isTimeoutInterruptedIOException(e)) { val resultTimeout = ResultSupreme.Timeout<T>(exception = e) resultTimeout.message = extractErrorMessage(e) resultTimeout } else { handleOthersException(e) } } private fun <T> resultSupremeGplayHttpRequestException(e: GplayHttpRequestException): ResultSupreme<T> { val message = extractErrorMessage(e) val exception = GPlayException(e.status == GPlayHttpClient.STATUS_CODE_TIMEOUT, message) Loading @@ -83,12 +97,20 @@ private fun extractErrorMessage(e: Exception): String { val status = when (e) { is GplayHttpRequestException -> e.status.toString() is SocketTimeoutException -> TIMEOUT is InterruptedIOException -> if (isTimeoutInterruptedIOException(e)) TIMEOUT else UNKNOWN else -> UNKNOWN } return (e.localizedMessage?.ifBlank { ERROR_GPLAY_API } ?: ERROR_GPLAY_API) + " $STATUS $status" } private fun isTimeoutInterruptedIOException(e: InterruptedIOException): Boolean { val message = e.message ?: return true return TIMEOUT_MESSAGE_PATTERNS.any { pattern -> message.contains(pattern, ignoreCase = true) } } suspend fun <T> retryWithBackoff(retryDelayInSeconds: Int = -1, operation: suspend () -> T): T? { var result: T? = null try { Loading
app/src/test/java/foundation/e/apps/data/NetworkHandlerTest.kt +34 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ import foundation.e.apps.data.playstore.utils.GplayHttpRequestException import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test import java.io.InterruptedIOException import java.net.SocketTimeoutException @OptIn(ExperimentalCoroutinesApi::class) Loading @@ -33,6 +34,39 @@ class NetworkHandlerTest { assertThat(result.exception).isSameInstanceAs(timeout) } @Test fun handleNetworkResult_convertsInterruptedTimeout() = runTest { val timeout = InterruptedIOException("timeout") val result = handleNetworkResult<String> { throw timeout } assertThat(result.isTimeout()).isTrue() assertThat(result.message).isEqualTo("timeout Status: Timeout") assertThat(result.exception).isSameInstanceAs(timeout) } @Test fun handleNetworkResult_convertsInterruptedTimedOutMessage() = runTest { val timeout = InterruptedIOException("Read timed out") val result = handleNetworkResult<String> { throw timeout } assertThat(result.isTimeout()).isTrue() assertThat(result.message).isEqualTo("Read timed out Status: Timeout") assertThat(result.exception).isSameInstanceAs(timeout) } @Test fun handleNetworkResult_mapsNonTimeoutInterruptedIoExceptionToError() = runTest { val interrupted = InterruptedIOException("stream closed") val result = handleNetworkResult<String> { throw interrupted } assertThat(result.isUnknownError()).isTrue() assertThat(result.message).isEqualTo("stream closed Status: Unknown") assertThat(result.exception).isSameInstanceAs(interrupted) } @Test fun handleNetworkResult_mapsHttp429ToError() = runTest { val exception = GplayHttpRequestException( Loading