Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/src/main/java/ir/kindnesswall/data/model/Either.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package ir.kindnesswall.data.model

sealed class Either<out A, out B> {
data class Left<out A> constructor(val left: A) : Either<A, Nothing>()
data class Right<out B> constructor(val right: B) : Either<Nothing, B>()
}
8 changes: 8 additions & 0 deletions app/src/main/java/ir/kindnesswall/data/model/Error.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package ir.kindnesswall.data.model

data class Error(
var message: String? = null,
var code: Int? = -1,
var errorBody: String? = null,
val serverError: Boolean = false,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package ir.kindnesswall.data.remote.adapter

import ir.kindnesswall.data.model.Either
import ir.kindnesswall.data.model.Error
import okhttp3.Request
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.lang.reflect.Type

internal class EitherCall<R>(
private val delegate: Call<R>,
private val successType: Type,
) : Call<Either<Error, R>> {

override fun enqueue(callback: Callback<Either<Error, R>>) {

delegate.enqueue(object : Callback<R> {
override fun onResponse(
call: Call<R>,
response: Response<R>,
) {
return callback.onResponse(this@EitherCall,
Response.success(mapResponseToEither(response)))
}

override fun onFailure(call: Call<R>, throwable: Throwable) {
callback.onResponse(
this@EitherCall,
Response.success(
Either.Left(
Error(
message = throwable.message ?: throwable.toString(),
serverError = true
)
)
)
)
}
})
}

private fun <T> mapResponseToEither(
response: Response<T>,
): Either<Error, T> {
when (response.code()) {
204 -> {
@Suppress("UNCHECKED_CAST")
return Either.Right(Unit) as Either<Error, T>
}
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)

override fun isCanceled() = delegate.isCanceled

override fun cancel() = delegate.cancel()

override fun execute(): Response<Either<Error, R>> =
throw UnsupportedOperationException("NetworkResponseCall doesn't support execute")

override fun request(): Request = delegate.request()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package ir.kindnesswall.data.remote.adapter

import ir.kindnesswall.data.model.Either
import ir.kindnesswall.data.model.Error
import retrofit2.Call
import retrofit2.CallAdapter
import java.lang.reflect.Type

internal class EitherCallAdapter<R>(
private val successType: Type,
) : CallAdapter<R, Call<Either<Error, R>>> {

@Suppress("UNCHECKED_CAST")
override fun adapt(
call: Call<R>
): Call<Either<Error, R>> =
EitherCall(call, successType)

override fun responseType(): Type = successType
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package ir.kindnesswall.data.remote.adapter

import ir.kindnesswall.data.model.Either
import ir.kindnesswall.data.model.Error
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<Annotation>,
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) != Error::class.java) return null

val rightType = getParameterUpperBound(1, responseType)

return EitherCallAdapter<Any>(rightType)
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -19,7 +21,7 @@ import retrofit2.http.Path

interface CharityApi {
@GET("charity/list")
suspend fun getCharities(): Response<List<CharityModel>>
suspend fun getCharities(): Either<Error, List<CharityModel>>

@GET("charity/user/{id}")
suspend fun getCharity(@Path("id") id: Long): Response<CharityModel>
Expand Down
34 changes: 15 additions & 19 deletions app/src/main/java/ir/kindnesswall/data/repository/CharityRepo.kt
Original file line number Diff line number Diff line change
@@ -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


Expand All @@ -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<List<CharityModel>>().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<List<CharityModel>>().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):
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/ir/kindnesswall/di/NetworkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -63,6 +64,7 @@ private fun retrofitClient(baseUrl: String, httpClient: OkHttpClient): Retrofit
.baseUrl(baseUrl)
.client(httpClient)
.addConverterFactory(NullOnEmptyConverterFactory())
.addCallAdapterFactory(EitherCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build()

Expand Down