Skip to content

Commit

Permalink
Use sandwich for constructing asynchronous request with API responses
Browse files Browse the repository at this point in the history
  • Loading branch information
skydoves committed May 5, 2020
1 parent 75f5f9d commit 9d0239c
Show file tree
Hide file tree
Showing 13 changed files with 54 additions and 143 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Go to the [Releases](https://github.com/skydoves/MarvelHeroes/releases) to downl
- [Koin](https://github.com/InsertKoinIO/koin) - dependency injection.
- [Retrofit2 & Gson](https://github.com/square/retrofit) - construct the REST APIs.
- [OkHttp3](https://github.com/square/okhttp) - implementing interceptor, logging and mocking web server.
- [Sandwich](https://github.com/skydoves/Sandwich) - construct lightweight http API response and handling error responses.
- [Glide](https://github.com/bumptech/glide) - loading images.
- [TransformationLayout](https://github.com/skydoves/transformationlayout) - implementing transformation motion animations.
- [WhatIf](https://github.com/skydoves/whatif) - checking nullable object and empty collections more fluently.
Expand Down
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ dependencies {
implementation "org.koin:koin-android-viewmodel:$versions.koinVersion"

// network
implementation "com.github.skydoves:sandwich:$versions.sandwichVersion"
implementation "com.squareup.retrofit2:retrofit:$versions.retrofitVersion"
implementation "com.squareup.okhttp3:logging-interceptor:$versions.okhttpVersion"
implementation "com.squareup.retrofit2:converter-gson:$versions.retrofitVersion"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@

package com.skydoves.marvelheroes.di

import com.skydoves.marvelheroes.model.Poster
import com.skydoves.marvelheroes.network.MarvelClient
import com.skydoves.marvelheroes.network.MarvelService
import com.skydoves.marvelheroes.network.RequestInterceptor
import com.skydoves.sandwich.ResponseDataSource
import okhttp3.OkHttpClient
import org.koin.dsl.module
import retrofit2.Retrofit
Expand All @@ -43,4 +45,6 @@ val networkModule = module {
single { get<Retrofit>().create(MarvelService::class.java) }

single { MarvelClient(get()) }

single { ResponseDataSource<List<Poster>>() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import org.koin.dsl.module

val repositoryModule = module {

single { MainRepository(get(), get()) }
single { MainRepository(get(), get(), get()) }

single { DetailRepository(get()) }
}
86 changes: 0 additions & 86 deletions app/src/main/java/com/skydoves/marvelheroes/network/ApiResponse.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,23 @@
package com.skydoves.marvelheroes.network

import com.skydoves.marvelheroes.model.Poster
import com.skydoves.sandwich.ApiResponse
import com.skydoves.sandwich.ResponseDataSource

class MarvelClient(private val marvelService: MarvelService) {

fun fetchMarvelPosters(
dataSource: ResponseDataSource<List<Poster>>,
onResult: (response: ApiResponse<List<Poster>>) -> Unit
) {
this.marvelService.fetchMarvelPosterList().transform(onResult)
dataSource
// retry fetching data 3 times with 5000 milli-seconds time interval when the request gets failure.
.retry(3, 5000L)
// combine network service to the data source.
.combine(marvelService.fetchMarvelPosterList(), onResult)
// request API network call asynchronously.
// if the request is successful, the data source will hold the success data.
// in the next request after success, returns the cached API response with data.
.request()
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,21 @@ package com.skydoves.marvelheroes.repository

import androidx.lifecycle.MutableLiveData
import com.skydoves.marvelheroes.model.Poster
import com.skydoves.marvelheroes.network.ApiResponse
import com.skydoves.marvelheroes.network.MarvelClient
import com.skydoves.marvelheroes.network.message
import com.skydoves.marvelheroes.persistence.PosterDao
import com.skydoves.sandwich.ResponseDataSource
import com.skydoves.sandwich.message
import com.skydoves.sandwich.onError
import com.skydoves.sandwich.onException
import com.skydoves.sandwich.onSuccess
import com.skydoves.whatif.whatIfNotNull
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber

class MainRepository constructor(
private val marvelClient: MarvelClient,
private val marvelDataSource: ResponseDataSource<List<Poster>>,
private val posterDao: PosterDao
) : Repository {

Expand All @@ -43,19 +47,23 @@ class MainRepository constructor(
var posters = posterDao.getPosterList()
if (posters.isEmpty()) {
isLoading = true
marvelClient.fetchMarvelPosters { response ->
marvelClient.fetchMarvelPosters(marvelDataSource) { apiResponse ->
isLoading = false
when (response) {
is ApiResponse.Success -> {
response.data.whatIfNotNull {
apiResponse
// handle the case when the API request gets a success response.
.onSuccess {
data.whatIfNotNull {
posters = it
liveData.postValue(it)
posterDao.insertPosterList(it)
}
}
is ApiResponse.Failure.Error -> error(response.message())
is ApiResponse.Failure.Exception -> error(response.message())
}
// handle the case when the API request gets a error response.
// e.g. internal server error.
.onError { error(message()) }
// handle the case when the API request gets a exception response.
// e.g. network connection error.
.onException { error(message()) }
}
}
liveData.apply { postValue(posters) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package com.skydoves.marvelheroes.network

import com.skydoves.sandwich.ApiResponse
import com.skydoves.sandwich.SandwichInitializer
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test
Expand All @@ -35,7 +37,8 @@ class ApiResponseTest {

@Test
fun success() {
val apiResponse = ApiResponse.of { Response.success("foo") }
val apiResponse =
ApiResponse.of(SandwichInitializer.successCodeRange) { Response.success("foo") }
if (apiResponse is ApiResponse.Success) {
assertThat(apiResponse.data, `is`("foo"))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package com.skydoves.marvelheroes.network
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
import com.skydoves.marvelheroes.model.Poster
import com.skydoves.sandwich.ApiResponse
import com.skydoves.sandwich.ResponseDataSource
import java.io.IOException
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.CoreMatchers.instanceOf
Expand Down Expand Up @@ -49,6 +51,7 @@ class MarvelServiceTest : ApiAbstract<MarvelService>() {
assertThat(responseBody[0].name, `is`("Deadpool"))
assertThat(responseBody[0].color, `is`("#770609"))

val dataSource = ResponseDataSource<List<Poster>>()
val onResult: (response: ApiResponse<List<Poster>>) -> Unit = {
assertThat(it, instanceOf(ApiResponse.Success::class.java))
val response: List<Poster> = requireNotNull((it as ApiResponse.Success).data)
Expand All @@ -57,11 +60,11 @@ class MarvelServiceTest : ApiAbstract<MarvelService>() {
assertThat(response[0].color, `is`("#770609"))
}

whenever(client.fetchMarvelPosters(onResult)).thenAnswer {
val response: (response: ApiResponse<List<Poster>>) -> Unit = it.getArgument(0)
whenever(client.fetchMarvelPosters(dataSource, onResult)).thenAnswer {
val response: (response: ApiResponse<List<Poster>>) -> Unit = it.getArgument(1)
response(ApiResponse.Success(Response.success(responseBody)))
}

client.fetchMarvelPosters(onResult)
client.fetchMarvelPosters(dataSource, onResult)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.skydoves.marvelheroes.network.MarvelClient
import com.skydoves.marvelheroes.network.MarvelService
import com.skydoves.marvelheroes.persistence.PosterDao
import com.skydoves.marvelheroes.utils.MockTestUtil.mockPosterList
import com.skydoves.sandwich.ResponseDataSource
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import org.junit.Before
Expand All @@ -44,6 +45,7 @@ class MainRepositoryTest {
private lateinit var client: MarvelClient
private val service: MarvelService = mock()
private val posterDao: PosterDao = mock()
private val dataSource: ResponseDataSource<List<Poster>> = ResponseDataSource()

@ExperimentalCoroutinesApi
@get:Rule
Expand All @@ -56,7 +58,7 @@ class MainRepositoryTest {
@Before
fun setup() {
client = MarvelClient(service)
repository = MainRepository(client, posterDao)
repository = MainRepository(client, dataSource, posterDao)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.skydoves.marvelheroes.persistence.PosterDao
import com.skydoves.marvelheroes.repository.MainRepository
import com.skydoves.marvelheroes.utils.MockTestUtil
import com.skydoves.marvelheroes.view.ui.main.MainViewModel
import com.skydoves.sandwich.ResponseDataSource
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import org.junit.Before
Expand All @@ -44,6 +45,7 @@ class MainViewModelTest {
private val marvelService: MarvelService = mock()
private val marvelClient: MarvelClient = MarvelClient(marvelService)
private val posterDao: PosterDao = mock()
private val dataSource: ResponseDataSource<List<Poster>> = mock()

@ExperimentalCoroutinesApi
@get:Rule
Expand All @@ -55,7 +57,7 @@ class MainViewModelTest {
@ExperimentalCoroutinesApi
@Before
fun setup() {
mainRepository = MainRepository(marvelClient, posterDao)
mainRepository = MainRepository(marvelClient, dataSource, posterDao)
viewModel = MainViewModel(mainRepository)
}

Expand Down
3 changes: 2 additions & 1 deletion dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ ext.versions = [
// network
retrofitVersion : '2.6.0',
okhttpVersion : '4.1.0',
sandwichVersion : '1.0.0',

// coroutines
coroutinesVersion : '1.3.0',

// glide
glideVersion : '4.9.0',
glideVersion : '4.10.0',

// transformation
transformationLayout: '1.0.4',
Expand Down

0 comments on commit 9d0239c

Please sign in to comment.