Skip to content

Commit

Permalink
Merge pull request #63 from swapnil-musale/response_handling
Browse files Browse the repository at this point in the history
Improvement in Network Layer Response Handling
  • Loading branch information
swapnil-musale committed Aug 27, 2023
2 parents c9f574f + 9e479d2 commit e23a0ab
Show file tree
Hide file tree
Showing 15 changed files with 98 additions and 57 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
.externalNativeBuild
.cxx
local.properties
deploymentTargetDropDown.xml
6 changes: 2 additions & 4 deletions app/src/main/java/com/devx/jetjoke/ui/home/HomeScreen.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.devx.jetjoke.ui.home

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
Expand Down Expand Up @@ -41,10 +40,9 @@ import com.devx.jetjoke.ui.home.component.JokeCategoryTag
import com.devx.jetjoke.ui.home.component.TypewriterText
import com.devx.jetjoke.ui.shimmer.JokeShimmerItem

@OptIn(ExperimentalAnimationApi::class)
@Composable
fun HomeScreen(
uiState: HomeScreenState,
uiState: HomeScreenUiState,
loadNextJoke: () -> Unit,
) {
var displayJoke by remember { mutableStateOf("") }
Expand Down Expand Up @@ -127,7 +125,7 @@ fun HomeScreen(
private fun HomeScreenPreview() {
JetJokeTheme {
HomeScreen(
uiState = HomeScreenState(
uiState = HomeScreenUiState(
isLoading = false,
jokeData = getFakeJokeData(),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package com.devx.jetjoke.ui.home

import com.devx.domain.model.JokeResponse

data class HomeScreenState(
data class HomeScreenUiState(
val isLoading: Boolean = false,
val jokeData: JokeResponse? = null,
val errorMessage: String? = null,
)
33 changes: 23 additions & 10 deletions app/src/main/java/com/devx/jetjoke/ui/home/HomeViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.devx.jetjoke.ui.home
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.devx.domain.useCase.GetJokeUseCase
import com.devx.domain.util.NetworkResponse
import com.devx.jetjoke.util.DispatchersProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
Expand All @@ -18,9 +19,9 @@ class HomeViewModel @Inject constructor(
private val dispatchersProvider: DispatchersProvider,
) : ViewModel() {

private val _uiState: MutableStateFlow<HomeScreenState> =
MutableStateFlow(value = HomeScreenState())
val uiState: StateFlow<HomeScreenState> = _uiState.asStateFlow()
private val _uiState: MutableStateFlow<HomeScreenUiState> =
MutableStateFlow(value = HomeScreenUiState())
val uiState: StateFlow<HomeScreenUiState> = _uiState.asStateFlow()

init {
fetchJoke()
Expand All @@ -31,13 +32,25 @@ class HomeViewModel @Inject constructor(
_uiState.update {
it.copy(isLoading = true)
}
val response = getJokeUseCase()
if (response.error?.not() == true) {
_uiState.update {
it.copy(
isLoading = false,
jokeData = response,
)
when (val response = getJokeUseCase()) {
is NetworkResponse.Success -> {
_uiState.update {
it.copy(
isLoading = false,
jokeData = response.data,
)
}
}

is NetworkResponse.Error -> {
// TODO Show Snack bar
_uiState.update {
it.copy(isLoading = false, errorMessage = response.errorMessage)
}
}

is NetworkResponse.Exception -> {
// TODO Show Snack bar
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions data/src/main/java/com/devx/data/JokeApi.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.devx.data

import com.devx.data.model.JokeResponse
import com.devx.data.model.JokeResponseDto
import retrofit2.Response
import retrofit2.http.GET

interface JokeApi {

@GET("joke/Any")
suspend fun getJoke(): Response<JokeResponse>
suspend fun getJoke(): Response<JokeResponseDto>
}
15 changes: 0 additions & 15 deletions data/src/main/java/com/devx/data/mapper/JokeResponse.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.devx.data.model

import androidx.annotation.Keep
import com.devx.domain.model.JokeResponse
import com.devx.domain.util.Mapper
import com.squareup.moshi.Json

@Keep
data class JokeResponse(
data class JokeResponseDto(

@field:Json(name = "delivery")
val delivery: String? = null,
Expand Down Expand Up @@ -35,4 +37,16 @@ data class JokeResponse(

@field:Json(name = "message")
val message: String? = null,
)
) : Mapper<JokeResponse> {
override fun mapToDomain(): JokeResponse {
return JokeResponse(
error = error ?: false,
type = type.orEmpty(),
setUp = setup,
joke = joke,
delivery = delivery,
category = category.orEmpty(),
message = message,
)
}
}
24 changes: 6 additions & 18 deletions data/src/main/java/com/devx/data/repository/JokeRepositoryImpl.kt
Original file line number Diff line number Diff line change
@@ -1,32 +1,20 @@
package com.devx.data.repository

import com.devx.data.JokeApi
import com.devx.data.mapper.toDto
import com.devx.data.model.JokeCategoryData
import com.devx.data.util.safeApiCall
import com.devx.domain.model.JokeResponse
import com.devx.domain.repository.JokeRepository
import com.devx.domain.util.NetworkResponse
import javax.inject.Inject

class JokeRepositoryImpl @Inject constructor(private val jokeApi: JokeApi) : JokeRepository {

override suspend fun getJoke(): JokeResponse {
return try {
val response = jokeApi.getJoke()
response.body().toDto()
} catch (exception: Exception) {
JokeResponse(
joke = "",
setUp = "",
delivery = "",
category = "",
type = "",
error = true,
message = exception.message,
)
}
override suspend fun getJoke(): NetworkResponse<JokeResponse> {
return safeApiCall { jokeApi.getJoke() }
}

override fun getJokeCategories(): ArrayList<String> {
return JokeCategoryData.jokeCategories
override fun getJokeCategories(): NetworkResponse<List<String>> {
return NetworkResponse.Success(data = JokeCategoryData.jokeCategories)
}
}
22 changes: 22 additions & 0 deletions data/src/main/java/com/devx/data/util/NetworkUtil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.devx.data.util

import com.devx.domain.util.Mapper
import com.devx.domain.util.NetworkResponse
import retrofit2.HttpException
import retrofit2.Response

suspend fun <R : Mapper<T>, T : Any> safeApiCall(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)
}
}
2 changes: 1 addition & 1 deletion domain/src/main/java/com/devx/domain/model/JokeResponse.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ data class JokeResponse(
fun getFakeJokeData(): JokeResponse {
return JokeResponse(
error = false,
type = "twopart", // "single"
type = "twopart",
setUp = "So what's a set of predefined steps the government might take to preserve the environment?",
delivery = "An Al-Gore-ithm.",
category = "Programming",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.devx.domain.repository

import com.devx.domain.model.JokeResponse
import com.devx.domain.util.NetworkResponse

interface JokeRepository {
suspend fun getJoke(): JokeResponse
fun getJokeCategories(): ArrayList<String>
suspend fun getJoke(): NetworkResponse<JokeResponse>
fun getJokeCategories(): NetworkResponse<List<String>>
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.devx.domain.useCase

import com.devx.domain.repository.JokeRepository
import com.devx.domain.util.NetworkResponse
import javax.inject.Inject

class GetJokeCategoriesUseCase @Inject constructor(private val jokeRepository: JokeRepository) {
operator fun invoke(): ArrayList<String> {
operator fun invoke(): NetworkResponse<List<String>> {
return jokeRepository.getJokeCategories()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package com.devx.domain.useCase

import com.devx.domain.model.JokeResponse
import com.devx.domain.repository.JokeRepository
import com.devx.domain.util.NetworkResponse
import javax.inject.Inject

class GetJokeUseCase @Inject constructor(private val jokeRepository: JokeRepository) {
suspend operator fun invoke(): JokeResponse {
suspend operator fun invoke(): NetworkResponse<JokeResponse> {
return jokeRepository.getJoke()
}
}
5 changes: 5 additions & 0 deletions domain/src/main/java/com/devx/domain/util/Mapper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.devx.domain.util

interface Mapper<T> {
fun mapToDomain(): T
}
11 changes: 11 additions & 0 deletions domain/src/main/java/com/devx/domain/util/NetworkResponse.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.devx.domain.util

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)
}

0 comments on commit e23a0ab

Please sign in to comment.