Skip to content

Releases: skydoves/sandwich

1.0.7

21 Dec 11:09
32adf03
Compare
Choose a tag to compare

馃帀 Released a new version 1.0.7! 馃帀

What's New?

  • Changed non-inline functions to inline classes.
  • Removed generating BuildConfig class.
  • Added ApiSuccessModelMapper for mapping data of the ApiResponse.Success to the custom model.
    We can map the ApiResponse.Success model to our custom model using the mapper extension.
object SuccessPosterMapper : ApiSuccessModelMapper<List<Poster>, Poster?> {

  override fun map(apiErrorResponse: ApiResponse.Success<List<Poster>>): Poster? {
    return apiErrorResponse.data?.first()
  }
}

// Maps the success response data.
val poster: Poster? = map(SuccessPosterMapper)

or

// Maps the success response data using a lambda.
map(SuccessPosterMapper) { poster ->
  livedata.post(poster) // we can use the `this` keyword instead.
}
  • Added mapOnSuccess and mapOnError extensions for mapping success/error model to the custom model in their scope.
  • Added merge extension for ApiResponse for merging multiple ApiResponses as one ApiResponse depending on the policy.
    The below example is merging three ApiResponse as one if every three ApiResponses are successful.
disneyService.fetchDisneyPosterList(page = 0).merge(
   disneyService.fetchDisneyPosterList(page = 1),
   disneyService.fetchDisneyPosterList(page = 2),
   mergePolicy = ApiResponseMergePolicy.PREFERRED_FAILURE
).onSuccess { 
  // handle response data..
}.onError { 
  // handle error..
}

ApiResponseMergePolicy

ApiResponseMergePolicy is a policy for merging response data depend on the success or not.

  • IGNORE_FAILURE: Regardless of the merging order, ignores failure responses in the responses.
  • PREFERRED_FAILURE (default): Regardless of the merging order, prefers failure responses in the responses.

1.0.6

25 Oct 15:40
8727210
Compare
Choose a tag to compare

馃帀 Released a new version 1.0.6! 馃帀

What's New?

  • Added a Disposable interface and disposable() extension for canceling tasks when we want.
  • Added DisposableComposite is that a disposable container that can hold onto multiple other disposables.
  • Added joinDisposable extensions to DataSource and Call for creating a disposable and add easily.

Disposable in Call

We can cancel the executing works using a disposable() extension.

val disposable = call.request { response ->
  // skip handling a response //
}.disposable()

// dispose the executing works
disposable.dispose()

And we can use CompositeDisposable for canceling multiple resources at once.

class MainViewModel constructor(disneyService: DisneyService) : ViewModel() {

  private val disposables = CompositeDisposable()

  init {
    disneyService.fetchDisneyPosterList()
      .joinDisposable(disposables) // joins onto [CompositeDisposable] as a disposable.
      .request {response ->
      // skip handling a response //
    }
  }

  override fun onCleared() {
    super.onCleared()
    if (!disposables.disposed) {
      disposables.clear()
    }
  }
}

Disposable in DataSource

We can make it joins onto CompositeDisposable as a disposable using the joinDisposable function. It must be called before request() method. The below example is using in ViewModel. We can clear the CompositeDisposable in the onCleared() override method.

private val disposable = CompositeDisposable()

init {
    disneyService.fetchDisneyPosterList().toResponseDataSource()
      // retry fetching data 3 times with 5000L interval when the request gets failure.
      .retry(3, 5000L)
      // joins onto CompositeDisposable as a disposable and dispose onCleared().
      .joinDisposable(disposable)
      .request {
        // ... //
      }
}

override fun onCleared() {
    super.onCleared()
    if (!disposable.disposed) {
      disposable.clear()
    }
  }

1.0.5

18 Sep 13:21
08556f2
Compare
Choose a tag to compare

馃帀 Released a new version 1.0.5! 馃帀

What's New?

  • Fixed: crashes on 400~500 error happened with using CoroutinesResponseCallAdapterFactory and CoroutinesDataSourceCallAdapterFactory. (#5)
  • Used kotlin 1.4.0 stable internally.
  • Used single abstract method conversions for interfaces.

1.0.4

17 Jul 11:44
9eefa84
Compare
Choose a tag to compare

馃帀 Released a new version 1.0.4! 馃帀

What's New?

Added suspendOnSuccess, suspendOnFailure, suspendOnException extensions of the ApiResponse.

We can use them for handling suspend functions inside the lambda.
In this case, we should use with CoroutinesResponseCallAdapterFactory.

flow {
      val response = disneyService.fetchDisneyPosterList()
      response.suspendOnSuccess {
        emit(data)
      }.suspendOnError {
        // stub error case
      }.suspendOnFailure {
        // stub exception case
      }
    }

1.0.3

26 Jun 15:24
2e16ec2
Compare
Choose a tag to compare

馃帀 Released a new version 1.0.3! 馃帀

What's New?

Now Sandwich supports using with coroutines.
We can use the suspend function on services.

CoroutinesResponseCallAdapterFactory

We can build the Retrofit using with CoroutinesResponseCallAdapterFactory() call adapter factory.

addCallAdapterFactory(CoroutinesResponseCallAdapterFactory())

And we can use the suspend keyword in our service.

interface DisneyCoroutinesService {

  @GET("DisneyPosters.json")
  suspend fun fetchDisneyPosterList(): ApiResponse<List<Poster>>
}

And now we can use like this; An example of using toLiveData.

class MainCoroutinesViewModel constructor(disneyService: DisneyCoroutinesService) : ViewModel() {

  val posterListLiveData: LiveData<List<Poster>>

  init {
    posterListLiveData = liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
      emitSource(disneyService.fetchDisneyPosterList()
        .onSuccess {
          Timber.d("$data")
        }
        .onError {
          Timber.d(message())
        }
        .onException {
          Timber.d(message())
        }.toLiveData())
    }

CoroutinesDataSourceCallAdapterFactory

We can build the Retrofit using with CoroutinesDataSourceCallAdapterFactory() call adapter factory.

addCallAdapterFactory(CoroutinesDataSourceCallAdapterFactory())

And we can get the response data as the DataSource type with suspend keyword.

interface DisneyCoroutinesService {

  @GET("DisneyPosters.json")
  suspend fun fetchDisneyPosterList(): DataSource<List<Poster>>
}

Here is an example of the using asLiveData.

class MainCoroutinesViewModel constructor(disneyService: DisneyCoroutinesService) : ViewModel() {

 val posterListLiveData: LiveData<List<Poster>>

 init {
    Timber.d("initialized MainViewModel.")

    posterListLiveData = liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
      emitSource(disneyService.fetchDisneyPosterList().toResponseDataSource()
        // retry fetching data 3 times with 5000L interval when the request gets failure.
       .retry(3, 5000L)
       .dataRetainPolicy(DataRetainPolicy.RETAIN)
       .request {
          // handle the case when the API request gets a success response.
          onSuccess {
            Timber.d("$data")
          }
    // -- skip --
    }.asLiveData())
 }

1.0.2

14 Jun 12:29
8275791
Compare
Choose a tag to compare

馃帀 Released a new version 1.0.2! 馃帀

RetainPolicy

We can limit the policy for retaining data on the temporarily internal storage.
The default policy is no retaining any fetched data from the network, but we can set the policy using the dataRetainPolicy method.

// Retain fetched data on the memory storage temporarily.
// If request again, returns the retained data instead of re-fetching from the network.
dataSource.dataRetainPolicy(DataRetainPolicy.RETAIN)

asLiveData

we can observe fetched data via DataSource as a LiveData.

  • if the response is successful, it returns a [LiveData] which contains response data.
  • if the response is failure or exception, it returns an empty [LiveData].
val posterListLiveData: LiveData<List<Poster>>

init {
    posterListLiveData = disneyService.fetchDisneyPosterList().toResponseDataSource()
      .retry(3, 5000L)
      .dataRetainPolicy(DataRetainPolicy.RETAIN)
      .request {
        // ...
      }.asLiveData()
  }

toResponseDataSource

If we use DataSourceCallAdapterFactory, we can only get the DataSource interface instead of ResponseDataSource. So in this release, there is a new way to change DataSource to ResponseDataSource.
We can change DataSource to ResponseDataSource after getting instance from network call using the below method.

private val dataSource: ResponseDataSource<List<Poster>>

  init {
    dataSource = disneyService.fetchDisneyPosterList().toResponseDataSource()

    //...
  }

1.0.1

07 May 13:32
1e758d3
Compare
Choose a tag to compare

Released a new version 1.0.1!

What's new?

  • Added DataSourceCallAdapterFactory.

We can get the DataSource directly from the Retrofit service.
Add call adapter factory DataSourceCallAdapterFactory to your Retrofit builder.
And change the return type of your service Call to DataSource.

Retrofit.Builder()
    ...
    .addCallAdapterFactory(DataSourceCallAdapterFactory())
    .build()

interface DisneyService {
  @GET("DisneyPosters.json")
  fun fetchDisneyPosterList(): DataSource<List<Poster>>
}

Here is the example of the DataSource in the MainViewModel.

class MainViewModel constructor(disneyService: DisneyService) : ViewModel() {

  // request API call Asynchronously and holding successful response data.
  private val dataSource: DataSource<List<Poster>>

    init {
    Timber.d("initialized MainViewModel.")

    dataSource = disneyService.fetchDisneyPosterList()
      // retry fetching data 3 times with 5000L interval when the request gets failure.
      .retry(3, 5000L)
      .observeResponse(object : ResponseObserver<List<Poster>> {
        override fun observe(response: ApiResponse<List<Poster>>) {
          // handle the case when the API request gets a success response.
          response.onSuccess {
            Timber.d("$data")
            posterListLiveData.postValue(data)
          }
        }
      })
      .request() // must call request()

1.0.0

05 May 06:29
03c6c0d
Compare
Choose a tag to compare

Released a first version 1.0.0!