I've build app using Clean Architecture now I'm planning to add Network Layer validation into it.
Below is the code how I have implemented this in my app.
interface JokeApi {
@GET("joke/Any")
suspend fun getJoke(): Response<JokeResponseDto>
}
class JokeRepositoryImpl @Inject constructor(private val jokeApi: JokeApi) : JokeRepository {
override suspend fun getJoke(): NetworkResponse<JokeResponse> {
return handleApiResponse { jokeApi.getJoke() }
}
}
NetworkResponse<JokeResponseDto>
suspend fun <T> handleApiResponse(execute: suspend () -> Response<T>): NetworkResponse<T> {
return try {
val response = execute()
val body = response.body()
if (response.isSuccessful && body != null) {
NetworkResponse.Success(data = body)
} else {
NetworkResponse.Error(errorMessage = response.message())
}
} catch (exception: HttpException) {
NetworkResponse.Error(errorMessage = exception.localizedMessage)
} catch (throwable: Throwable) {
NetworkResponse.Exception(throwable = throwable)
}
}
sealed class NetworkResponse<T>(
val data: T? = null,
val errorMessage: String? = null,
val exception: Throwable? = null,
) {
class Success<T>(data: T) : NetworkResponse<T>(data = data)
class Error<T>(errorMessage: String?) : NetworkResponse<T>(errorMessage = errorMessage)
class Exception<T>(throwable: Throwable) : NetworkResponse<T>(exception = throwable)
}
Issue in this is that retrofit returns the JokeResponseDto
response object where as getJoke
function of JokeRepositoryImpl
expect JokeResponse
which is valid as case as in this code mapping
functionality is missing.
I've tried myself to add mapping in handleApiResponse method as it's generic type so not getting much thought on implementation.
Below is the how I'm trying to convert response into another object with necessary details only.
class JokeResponseMapper : Mapper<JokeResponseDto, JokeResponse> {
override fun mapFrom(from: JokeResponseDto): JokeResponse {
return JokeResponse(
error = from.error,
type = from.type.orEmpty(),
setUp = from.type,
joke = from.joke,
)
}
}
interface Mapper<F, T> {
fun mapFrom(from: F): T
}
Any help with the code would be appreciated.
You can create an interface that maps the response object to the domain model:
interface DtoModel<T> {
fun mapToDomain(): T
}
Then change the handleApiResponse like this:
suspend fun <R: DtoModel<T>, T: Any> handleApiResponse(execute: suspend () -> Response<R>): NetworkResponse<T> {
return try {
val response = execute()
val body = response.body()
if (response.isSuccessful && body != null) {
NetworkResponse.Success(data = body.mapToDomain())
} else {
NetworkResponse.Error(errorMessage = response.message())
}
} catch (exception: HttpException) {
NetworkResponse.Error(errorMessage = exception.localizedMessage)
} catch (throwable: Throwable) {
NetworkResponse.Exception(throwable = throwable)
}
}
And call it like this:
return handleApiResponse { jokeApi.getJoke() }
Now the JokeResponseDto has to implements this interface and you have to implement the mapToDomain function inside there.
e.g:
class JokeResponseDto(
....
....
): DtoModel<JokeResponse> {
override fun mapToDomain(): JokeResponse {
return JokeResponse(
...
)
}
}