From 3cfb431b44c051541421baade8c5371e401bb2f4 Mon Sep 17 00:00:00 2001 From: Behzad Nematzadeh Date: Fri, 26 Aug 2022 15:01:28 +0430 Subject: [PATCH 1/9] add either class --- app/src/main/java/ir/kindnesswall/data/model/Either.kt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 app/src/main/java/ir/kindnesswall/data/model/Either.kt diff --git a/app/src/main/java/ir/kindnesswall/data/model/Either.kt b/app/src/main/java/ir/kindnesswall/data/model/Either.kt new file mode 100644 index 00000000..76d0a522 --- /dev/null +++ b/app/src/main/java/ir/kindnesswall/data/model/Either.kt @@ -0,0 +1,6 @@ +package ir.kindnesswall.data.model + +sealed class Either { + data class Left constructor(val left: A) : Either() + data class Right constructor(val right: B?) : Either() +} \ No newline at end of file From d1d12b7309b92ea172227b8f86096479109caaf6 Mon Sep 17 00:00:00 2001 From: Behzad Nematzadeh Date: Fri, 26 Aug 2022 15:02:00 +0430 Subject: [PATCH 2/9] add api-error sealed-class --- .../main/java/ir/kindnesswall/data/model/ApiError.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 app/src/main/java/ir/kindnesswall/data/model/ApiError.kt diff --git a/app/src/main/java/ir/kindnesswall/data/model/ApiError.kt b/app/src/main/java/ir/kindnesswall/data/model/ApiError.kt new file mode 100644 index 00000000..2a465351 --- /dev/null +++ b/app/src/main/java/ir/kindnesswall/data/model/ApiError.kt @@ -0,0 +1,11 @@ +package ir.kindnesswall.data.model + +sealed class ApiError +data class HttpError( + var message: String? = null, + var code: Int? = -1, + var errorBody: String? = null, + val serverError: Boolean = false +) : ApiError() + +object NetworkError : ApiError() \ No newline at end of file From 5f33235a77bb044c41f78fb3ed510f207bcd2e52 Mon Sep 17 00:00:00 2001 From: Behzad Nematzadeh Date: Fri, 26 Aug 2022 15:04:09 +0430 Subject: [PATCH 3/9] add call-adapter-factory by Either type --- .../data/remote/adapter/EitherCall.kt | 76 +++++++++++++++++++ .../data/remote/adapter/EitherCallAdapter.kt | 20 +++++ .../adapter/EitherCallAdapterFactory.kt | 32 ++++++++ 3 files changed, 128 insertions(+) create mode 100644 app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCall.kt create mode 100644 app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCallAdapter.kt create mode 100644 app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCallAdapterFactory.kt diff --git a/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCall.kt b/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCall.kt new file mode 100644 index 00000000..1fa1069b --- /dev/null +++ b/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCall.kt @@ -0,0 +1,76 @@ +package ir.kindnesswall.data.remote.adapter + +import ir.kindnesswall.data.model.Either +import ir.kindnesswall.data.model.ApiError +import ir.kindnesswall.data.model.HttpError +import ir.kindnesswall.data.model.NetworkError +import okhttp3.Request +import retrofit2.* +import java.lang.reflect.Type + +internal class EitherCall( + private val delegate: Call, + private val successType: Type +) : Call> { + + override fun enqueue(callback: Callback>) { + + delegate.enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + callback.onResponse(this@EitherCall, Response.success(response.toEither())) + } + + private fun Response.toEither(): Either { + if (!isSuccessful) { + errorBody()?.let { + return Either.Left( + HttpError(message(), code(), errorBody().toString()) + ) + } + } + body()?.let { body -> return Either.Right(body) } + return if (successType == Unit::class.java) { + @Suppress("UNCHECKED_CAST") + Either.Right(Unit) as Either + } else { + @Suppress("UNCHECKED_CAST") + Either.Left(UnknownError("Response body was null")) as Either + } + } + + override fun onFailure(call: Call, throwable: Throwable) { + callback.onResponse( + this@EitherCall, + Response.success( + Either.Left( + if (throwable.message!!.contains("Unable to resolve host")) { + NetworkError + } else { + HttpError( + message = throwable.message ?: throwable.toString(), + serverError = true + ) + } + ) + ) + ) + } + }) + } + + override fun isExecuted() = delegate.isExecuted + + override fun clone() = EitherCall(delegate.clone(), successType) + + override fun isCanceled() = delegate.isCanceled + + override fun cancel() = delegate.cancel() + + override fun execute(): Response> = + throw UnsupportedOperationException("NetworkResponseCall doesn't support execute") + + override fun request(): Request = delegate.request() +} \ No newline at end of file diff --git a/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCallAdapter.kt b/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCallAdapter.kt new file mode 100644 index 00000000..8f366c1a --- /dev/null +++ b/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCallAdapter.kt @@ -0,0 +1,20 @@ +package ir.kindnesswall.data.remote.adapter + +import ir.kindnesswall.data.model.Either +import ir.kindnesswall.data.model.ApiError +import retrofit2.Call +import retrofit2.CallAdapter +import java.lang.reflect.Type + +internal class EitherCallAdapter( + private val successType: Type, +) : CallAdapter>> { + + @Suppress("UNCHECKED_CAST") + override fun adapt( + call: Call + ): Call> = + EitherCall(call, successType) + + override fun responseType(): Type = successType +} \ No newline at end of file diff --git a/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCallAdapterFactory.kt b/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCallAdapterFactory.kt new file mode 100644 index 00000000..381c622c --- /dev/null +++ b/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCallAdapterFactory.kt @@ -0,0 +1,32 @@ +package ir.kindnesswall.data.remote.adapter + +import ir.kindnesswall.data.model.Either +import ir.kindnesswall.data.model.ApiError +import retrofit2.Call +import retrofit2.CallAdapter +import retrofit2.Retrofit +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type + +internal class EitherCallAdapterFactory : CallAdapter.Factory() { + + override fun get( + returnType: Type, + annotations: Array, + retrofit: Retrofit, + ): CallAdapter<*, *>? { + if (getRawType(returnType) != Call::class.java) return null + check(returnType is ParameterizedType) { "Return type must be a parameterized type." } + + val responseType = getParameterUpperBound(0, returnType) + if (getRawType(responseType) != Either::class.java) return null + check(responseType is ParameterizedType) { "Response type must be a parameterized type." } + + val leftType = getParameterUpperBound(0, responseType) + if (getRawType(leftType) != ApiError::class.java) return null + + val rightType = getParameterUpperBound(1, responseType) + + return EitherCallAdapter(rightType) + } +} \ No newline at end of file From 33dd860db12099d3476e2b90428487eaabf59b34 Mon Sep 17 00:00:00 2001 From: Behzad Nematzadeh Date: Fri, 26 Aug 2022 15:46:09 +0430 Subject: [PATCH 4/9] improve either-call --- .../data/remote/adapter/EitherCall.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCall.kt b/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCall.kt index 1fa1069b..4db68cd2 100644 --- a/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCall.kt +++ b/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCall.kt @@ -46,13 +46,14 @@ internal class EitherCall( this@EitherCall, Response.success( Either.Left( - if (throwable.message!!.contains("Unable to resolve host")) { - NetworkError - } else { - HttpError( - message = throwable.message ?: throwable.toString(), - serverError = true - ) + throwable.message?.let { + if (it.contains("Unable to resolve host")) { + NetworkError + } else { + HttpError(message = it, serverError = true) + } + } ?: kotlin.run { + HttpError(message = throwable.toString(), serverError = true) } ) ) From 495fdb7682a2ddfed009afe948766970616b7af2 Mon Sep 17 00:00:00 2001 From: Behzad Nematzadeh Date: Mon, 5 Sep 2022 23:22:00 +0430 Subject: [PATCH 5/9] remove nullable right-side --- app/src/main/java/ir/kindnesswall/data/model/Either.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/ir/kindnesswall/data/model/Either.kt b/app/src/main/java/ir/kindnesswall/data/model/Either.kt index 76d0a522..961d017d 100644 --- a/app/src/main/java/ir/kindnesswall/data/model/Either.kt +++ b/app/src/main/java/ir/kindnesswall/data/model/Either.kt @@ -2,5 +2,5 @@ package ir.kindnesswall.data.model sealed class Either { data class Left constructor(val left: A) : Either() - data class Right constructor(val right: B?) : Either() + data class Right constructor(val right: B) : Either() } \ No newline at end of file From f7efcf5cf16fe2eba70fb3b25b305ecba76bf8d0 Mon Sep 17 00:00:00 2001 From: Behzad Nematzadeh Date: Mon, 5 Sep 2022 23:23:07 +0430 Subject: [PATCH 6/9] add adapter-factory for retrofit client --- app/src/main/java/ir/kindnesswall/di/NetworkModule.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/ir/kindnesswall/di/NetworkModule.kt b/app/src/main/java/ir/kindnesswall/di/NetworkModule.kt index 58849ca6..e8926832 100644 --- a/app/src/main/java/ir/kindnesswall/di/NetworkModule.kt +++ b/app/src/main/java/ir/kindnesswall/di/NetworkModule.kt @@ -3,6 +3,7 @@ package ir.kindnesswall.di import android.content.Context import ir.kindnesswall.BuildConfig import ir.kindnesswall.data.local.UserInfoPref +import ir.kindnesswall.data.remote.adapter.EitherCallAdapterFactory import ir.kindnesswall.utils.extentions.addDebugInterceptor import ir.kindnesswall.utils.wrapInBearer import okhttp3.Interceptor @@ -63,6 +64,7 @@ private fun retrofitClient(baseUrl: String, httpClient: OkHttpClient): Retrofit .baseUrl(baseUrl) .client(httpClient) .addConverterFactory(NullOnEmptyConverterFactory()) + .addCallAdapterFactory(EitherCallAdapterFactory()) .addConverterFactory(GsonConverterFactory.create()) .build() From 01a019dbafc97c938f900aa965e20ce80afaa666 Mon Sep 17 00:00:00 2001 From: Behzad Nematzadeh Date: Mon, 5 Sep 2022 23:27:56 +0430 Subject: [PATCH 7/9] rename & repackage --- .../data/model/{ApiError.kt => Error.kt} | 9 +++---- .../data/remote/adapter/EitherCall.kt | 26 +++++++------------ .../data/remote/adapter/EitherCallAdapter.kt | 6 ++--- .../adapter/EitherCallAdapterFactory.kt | 4 +-- 4 files changed, 18 insertions(+), 27 deletions(-) rename app/src/main/java/ir/kindnesswall/data/model/{ApiError.kt => Error.kt} (50%) diff --git a/app/src/main/java/ir/kindnesswall/data/model/ApiError.kt b/app/src/main/java/ir/kindnesswall/data/model/Error.kt similarity index 50% rename from app/src/main/java/ir/kindnesswall/data/model/ApiError.kt rename to app/src/main/java/ir/kindnesswall/data/model/Error.kt index 2a465351..822d9d63 100644 --- a/app/src/main/java/ir/kindnesswall/data/model/ApiError.kt +++ b/app/src/main/java/ir/kindnesswall/data/model/Error.kt @@ -1,11 +1,8 @@ package ir.kindnesswall.data.model -sealed class ApiError -data class HttpError( +data class Error( var message: String? = null, var code: Int? = -1, var errorBody: String? = null, - val serverError: Boolean = false -) : ApiError() - -object NetworkError : ApiError() \ No newline at end of file + val serverError: Boolean = false, +) \ No newline at end of file diff --git a/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCall.kt b/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCall.kt index 4db68cd2..ca7a6c44 100644 --- a/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCall.kt +++ b/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCall.kt @@ -1,9 +1,7 @@ package ir.kindnesswall.data.remote.adapter import ir.kindnesswall.data.model.Either -import ir.kindnesswall.data.model.ApiError -import ir.kindnesswall.data.model.HttpError -import ir.kindnesswall.data.model.NetworkError +import ir.kindnesswall.data.model.Error import okhttp3.Request import retrofit2.* import java.lang.reflect.Type @@ -11,9 +9,9 @@ import java.lang.reflect.Type internal class EitherCall( private val delegate: Call, private val successType: Type -) : Call> { +) : Call> { - override fun enqueue(callback: Callback>) { + override fun enqueue(callback: Callback>) { delegate.enqueue(object : Callback { override fun onResponse( @@ -23,21 +21,21 @@ internal class EitherCall( callback.onResponse(this@EitherCall, Response.success(response.toEither())) } - private fun Response.toEither(): Either { + private fun Response.toEither(): Either { if (!isSuccessful) { errorBody()?.let { return Either.Left( - HttpError(message(), code(), errorBody().toString()) + Error(message(), code(), errorBody().toString()) ) } } body()?.let { body -> return Either.Right(body) } return if (successType == Unit::class.java) { @Suppress("UNCHECKED_CAST") - Either.Right(Unit) as Either + Either.Right(Unit) as Either } else { @Suppress("UNCHECKED_CAST") - Either.Left(UnknownError("Response body was null")) as Either + Either.Left(UnknownError("Response body was null")) as Either } } @@ -47,13 +45,9 @@ internal class EitherCall( Response.success( Either.Left( throwable.message?.let { - if (it.contains("Unable to resolve host")) { - NetworkError - } else { - HttpError(message = it, serverError = true) - } + Error(message = it, serverError = true) } ?: kotlin.run { - HttpError(message = throwable.toString(), serverError = true) + Error(message = throwable.toString(), serverError = true) } ) ) @@ -70,7 +64,7 @@ internal class EitherCall( override fun cancel() = delegate.cancel() - override fun execute(): Response> = + override fun execute(): Response> = throw UnsupportedOperationException("NetworkResponseCall doesn't support execute") override fun request(): Request = delegate.request() diff --git a/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCallAdapter.kt b/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCallAdapter.kt index 8f366c1a..d7fe2dd2 100644 --- a/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCallAdapter.kt +++ b/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCallAdapter.kt @@ -1,19 +1,19 @@ package ir.kindnesswall.data.remote.adapter import ir.kindnesswall.data.model.Either -import ir.kindnesswall.data.model.ApiError +import ir.kindnesswall.data.model.Error import retrofit2.Call import retrofit2.CallAdapter import java.lang.reflect.Type internal class EitherCallAdapter( private val successType: Type, -) : CallAdapter>> { +) : CallAdapter>> { @Suppress("UNCHECKED_CAST") override fun adapt( call: Call - ): Call> = + ): Call> = EitherCall(call, successType) override fun responseType(): Type = successType diff --git a/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCallAdapterFactory.kt b/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCallAdapterFactory.kt index 381c622c..046f1eed 100644 --- a/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCallAdapterFactory.kt +++ b/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCallAdapterFactory.kt @@ -1,7 +1,7 @@ package ir.kindnesswall.data.remote.adapter import ir.kindnesswall.data.model.Either -import ir.kindnesswall.data.model.ApiError +import ir.kindnesswall.data.model.Error import retrofit2.Call import retrofit2.CallAdapter import retrofit2.Retrofit @@ -23,7 +23,7 @@ internal class EitherCallAdapterFactory : CallAdapter.Factory() { check(responseType is ParameterizedType) { "Response type must be a parameterized type." } val leftType = getParameterUpperBound(0, responseType) - if (getRawType(leftType) != ApiError::class.java) return null + if (getRawType(leftType) != Error::class.java) return null val rightType = getParameterUpperBound(1, responseType) From 726cf73d9bef9633ee37c8410505a8220fefe476 Mon Sep 17 00:00:00 2001 From: Behzad Nematzadeh Date: Mon, 5 Sep 2022 23:28:57 +0430 Subject: [PATCH 8/9] improve EitherCall.kt --- .../data/remote/adapter/EitherCall.kt | 64 +++++++++++-------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCall.kt b/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCall.kt index ca7a6c44..a4c39b42 100644 --- a/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCall.kt +++ b/app/src/main/java/ir/kindnesswall/data/remote/adapter/EitherCall.kt @@ -3,12 +3,14 @@ package ir.kindnesswall.data.remote.adapter import ir.kindnesswall.data.model.Either import ir.kindnesswall.data.model.Error import okhttp3.Request -import retrofit2.* +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response import java.lang.reflect.Type internal class EitherCall( private val delegate: Call, - private val successType: Type + private val successType: Type, ) : Call> { override fun enqueue(callback: Callback>) { @@ -16,27 +18,10 @@ internal class EitherCall( delegate.enqueue(object : Callback { override fun onResponse( call: Call, - response: Response + response: Response, ) { - callback.onResponse(this@EitherCall, Response.success(response.toEither())) - } - - private fun Response.toEither(): Either { - if (!isSuccessful) { - errorBody()?.let { - return Either.Left( - Error(message(), code(), errorBody().toString()) - ) - } - } - body()?.let { body -> return Either.Right(body) } - return if (successType == Unit::class.java) { - @Suppress("UNCHECKED_CAST") - Either.Right(Unit) as Either - } else { - @Suppress("UNCHECKED_CAST") - Either.Left(UnknownError("Response body was null")) as Either - } + return callback.onResponse(this@EitherCall, + Response.success(mapResponseToEither(response))) } override fun onFailure(call: Call, throwable: Throwable) { @@ -44,11 +29,10 @@ internal class EitherCall( this@EitherCall, Response.success( Either.Left( - throwable.message?.let { - Error(message = it, serverError = true) - } ?: kotlin.run { - Error(message = throwable.toString(), serverError = true) - } + Error( + message = throwable.message ?: throwable.toString(), + serverError = true + ) ) ) ) @@ -56,6 +40,32 @@ internal class EitherCall( }) } + private fun mapResponseToEither( + response: Response, + ): Either { + when (response.code()) { + 204 -> { + @Suppress("UNCHECKED_CAST") + return Either.Right(Unit) as Either + } + in 200 until 300 -> { + val body = response.body() + return if (body != null) Either.Right(body) + else Either.Left(Error(message = "Response body was null")) + } + 503 -> { + return Either.Left(Error(message = response.message(), serverError = true)) + } + else -> { + return Either.Left( + Error(response.message(), + response.code(), + response.errorBody().toString()) + ) + } + } + } + override fun isExecuted() = delegate.isExecuted override fun clone() = EitherCall(delegate.clone(), successType) From 841927b3dac88d015062a12a269173fb50374508 Mon Sep 17 00:00:00 2001 From: Behzad Nematzadeh Date: Mon, 5 Sep 2022 23:29:23 +0430 Subject: [PATCH 9/9] change response-type for getCharities api --- .../data/remote/network/CharityApi.kt | 4 ++- .../data/repository/CharityRepo.kt | 34 ++++++++----------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/ir/kindnesswall/data/remote/network/CharityApi.kt b/app/src/main/java/ir/kindnesswall/data/remote/network/CharityApi.kt index 16ca0a3c..82ade45c 100644 --- a/app/src/main/java/ir/kindnesswall/data/remote/network/CharityApi.kt +++ b/app/src/main/java/ir/kindnesswall/data/remote/network/CharityApi.kt @@ -1,6 +1,8 @@ package ir.kindnesswall.data.remote.network import ir.kindnesswall.data.local.dao.charity.CharityModel +import ir.kindnesswall.data.model.Either +import ir.kindnesswall.data.model.Error import ir.kindnesswall.data.model.ReportCharityMessageModel import retrofit2.Response import retrofit2.http.Body @@ -19,7 +21,7 @@ import retrofit2.http.Path interface CharityApi { @GET("charity/list") - suspend fun getCharities(): Response> + suspend fun getCharities(): Either> @GET("charity/user/{id}") suspend fun getCharity(@Path("id") id: Long): Response diff --git a/app/src/main/java/ir/kindnesswall/data/repository/CharityRepo.kt b/app/src/main/java/ir/kindnesswall/data/repository/CharityRepo.kt index a14d0ad2..ad18901a 100644 --- a/app/src/main/java/ir/kindnesswall/data/repository/CharityRepo.kt +++ b/app/src/main/java/ir/kindnesswall/data/repository/CharityRepo.kt @@ -1,19 +1,17 @@ package ir.kindnesswall.data.repository import android.content.Context -import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.liveData import androidx.lifecycle.map import ir.kindnesswall.data.local.dao.charity.CharityModel import ir.kindnesswall.data.model.BaseDataSource -import ir.kindnesswall.data.model.ReportCharityMessageModel import ir.kindnesswall.data.model.CustomResult -import ir.kindnesswall.data.model.requestsmodel.DonateGiftRequestModel +import ir.kindnesswall.data.model.Either +import ir.kindnesswall.data.model.ReportCharityMessageModel import ir.kindnesswall.data.remote.network.CharityApi import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect @@ -36,22 +34,20 @@ class CharityRepo(context: Context, var charityApi: CharityApi) : BaseDataSource ) { emit(CustomResult.loading()) - getResultWithExponentialBackoffStrategy { charityApi.getCharities() } - .collect { result -> - when (result.status) { - CustomResult.Status.SUCCESS -> { - if (result.data == null) { - emit(CustomResult.error(result.errorMessage)) - } else { - emitSource(MutableLiveData>().apply { - value = result.data - }.map { CustomResult.success(it) }) - } - } - CustomResult.Status.LOADING -> emit(CustomResult.loading()) - else -> emit(CustomResult.error(result.errorMessage)) - } + when (val result = charityApi.getCharities()) { + is Either.Right -> { + emitSource(MutableLiveData>().apply { + value = result.right + }.map { CustomResult.success(it) }) } + is Either.Left -> { + emit(CustomResult.error( + CustomResult.ErrorMessage(message = result.left.message, + code = result.left.code, + errorBody = result.left.errorBody) + )) + } + } } fun getCharity(viewModelScope: CoroutineScope, charityId: Long):