Giter Site home page Giter Site logo

skydoves / retrofit-adapters Goto Github PK

View Code? Open in Web Editor NEW
465.0 7.0 17.0 403 KB

๐Ÿš† Retrofit call adapters for modeling network responses using Kotlin Result, Jetpack Paging3, and Arrow Either.

License: Apache License 2.0

Kotlin 100.00%
android arrow-kt coroutines either jetpack kotlin network paging3 retrofit retrofit2

retrofit-adapters's Introduction

Retrofit Adapters


License API Build Status Profile Dokka

๐Ÿš† Retrofit adapters for modeling network responses with Kotlin Result, Jetpack Paging3, and Arrow Either.

Sandwich

If you're interested in a more specified and lightweight Monad sealed API library for modeling Retrofit responses and handling exceptions, check out Sandwich.

Kotlin's Result

This library allows you to model your Retrofit responses with Kotlin's Result class.

Maven Central

Add the dependency below to your module's build.gradle file:

dependencies {
    implementation "com.github.skydoves:retrofit-adapters-result:1.0.9"
}

ResultCallAdapterFactory

You can return Kotlin's Result class to the Retrofit's service methods by setting ResultCallAdapterFactory like the below:

val retrofit: Retrofit = Retrofit.Builder()
    .baseUrl("BASE_URL")
    .addConverterFactory(..)
    .addCallAdapterFactory(ResultCallAdapterFactory.create())
    .build()

Then you can return the Result class with the suspend keyword.

interface PokemonService {

  @GET("pokemon")
  suspend fun fetchPokemonList(
    @Query("limit") limit: Int = 20,
    @Query("offset") offset: Int = 0
  ): Result<PokemonResponse>
}

Finally, you will get the network response, which is wrapped by the Result class like the below:

viewModelScope.launch {
  val result = pokemonService.fetchPokemonList()
  if (result.isSuccess) {
    val data = result.getOrNull()
    // handle data
  } else {
    // handle error case
  }
}

Empty Content Response

You can confine the response type as Unit when you need to handle empty body (content) API requests like the below:

@POST("/users/info")
suspend fun updateUserInfo(@Body userRequest: UserRequest): Result<Unit>

Unit Tests by Injecting TestScope

You can also inject your custom CoroutineScope into the ResultCallAdapterFactory and execute network requests on the scope.

val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()
val testScope = TestScope(testDispatcher)
val retrofit: Retrofit = Retrofit.Builder()
  .baseUrl("BASE_URL")
  .addConverterFactory(..)
  .addCallAdapterFactory(ResultCallAdapterFactory.create(testScope))
  .build()

Note: For more information about the Testing coroutines, check out the Testing Kotlin coroutines on Android.

Jetpack's Paging

This library allows you to return the paging source, which is parts of the Jetpack's Paging library.

Maven Central

Add the dependency below to your module's build.gradle file:

dependencies {
    implementation "com.github.skydoves:retrofit-adapters-paging:<version>"
}

PagingCallAdapterFactory

You can return Jetpack's PagingSource class to the Retrofit's service methods by setting PagingCallAdapterFactory like the below:

val retrofit: Retrofit = Retrofit.Builder()
    .baseUrl("BASE_URL")
    .addConverterFactory(..)
    .addCallAdapterFactory(PagingCallAdapterFactory.create())
    .build()

Then you can return the NetworkPagingSource class with the @PagingKeyConfig and @PagingKey annotations:

interface PokemonService {

  @GET("pokemon")
  @PagingKeyConfig(
    keySize = 20,
    mapper = PokemonPagingMapper::class
  )
  suspend fun fetchPokemonListAsPagingSource(
    @Query("limit") limit: Int = 20,
    @PagingKey @Query("offset") offset: Int = 0,
  ): NetworkPagingSource<PokemonResponse, Pokemon>
}

PagingKeyConfig and PagingKey

To return the NetworkPagingSource class, you must attach the @PagingKeyConfig and @PagingKey annotations to your Retrofit's service methods.

  • @PagingKeyConfig: Contains paging configurations for the network request and delivery them to the call adapter internally. You should set the keySize and mapper parameters.
  • @PagingKey: Marks the parameter in the service interface method as the paging key. This parameter will be paged by incrementing the page values continuously.

PagingMapper

You should create a paging mapper class, which extends the PagingMapper<T, R> interface like the below for transforming the original network response to the list of paging items. This class should be used in the @PagingKeyConfig annotation.

class PokemonPagingMapper : PagingMapper<PokemonResponse, Pokemon> {

  override fun map(value: PokemonResponse): List<Pokemon> {
    return value.results
  }
}

You will get the network response, which is wrapped by the NetworkPagingSource class like the below:

viewModelScope.launch {
  val pagingSource = pokemonService.fetchPokemonListAsPagingSource()
  val pagerFlow = Pager(PagingConfig(pageSize = 20)) { pagingSource }.flow
  stateFlow.emitAll(pagerFlow)
}

Finally, you should call the submitData method by your PagingDataAdapter to bind the paging data. If you want to learn more about the Jetpack's Paging, check out the Paging library.

Arrow's Either

This library allows you to model your Retrofit responses with arrow-kt's Either class.

Maven Central

Add the dependency below to your module's build.gradle file:

dependencies {
    implementation "com.github.skydoves:retrofit-adapters-arrow:<version>"
}

EitherCallAdapterFactory

You can return Arrow's Either class to the Retrofit's service methods by setting EitherCallAdapterFactory like the below:

val retrofit: Retrofit = Retrofit.Builder()
    .baseUrl("BASE_URL")
    .addConverterFactory(..)
    .addCallAdapterFactory(EitherCallAdapterFactory.create())
    .build()

Then you can return the Either class with the suspend keyword.

interface PokemonService {

  @GET("pokemon")
  suspend fun fetchPokemonListAsEither(
    @Query("limit") limit: Int = 20,
    @Query("offset") offset: Int = 0
  ): Either<Throwable, PokemonResponse>
}

Finally, you will get the network response, which is wrapped by the Either class like the below:

viewModelScope.launch {
  val either = pokemonService.fetchPokemonListAsEither()
  if (either.isRight()) {
    val data = either.orNull()
    // handle data
  } else {
    // handle error case
  }
}

Empty Content Response

You can confine the response type as Unit when you need to handle empty body (content) API requests like the below:

@POST("/users/info")
suspend fun updateUserInfo(@Body userRequest: UserRequest): Either<Throwable, Unit>

Unit Tests by Injecting TestScope

You can also inject your custom CoroutineScope into the EitherCallAdapterFactory and execute network requests on the scope.

val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()
val testScope = TestScope(testDispatcher)
val retrofit: Retrofit = Retrofit.Builder()
  .baseUrl("BASE_URL")
  .addConverterFactory(..)
  .addCallAdapterFactory(EitherCallAdapterFactory.create(testScope))
  .build()

Note: For more information about the Testing coroutines, check out the Testing Kotlin coroutines on Android.

Kotlin Serialization

This library allows you to deserialize your error body of the Retrofit response as your custom error class with Kotlin's Serialization.

For more information about setting up the plugin and dependency, check out Kotlin's Serialization.

Maven Central

Add the dependency below to your module's build.gradle file:

dependencies {
    implementation "com.github.skydoves:retrofit-adapters-serialization:<version>"
}

Deserialize Error Body

You can deserialize your error body with the deserializeHttpError extension and your custom error class. First, define your custom error class following your RESTful API formats as seen in the below:

@Serializable
public data class ErrorMessage(
  val code: Int,
  val message: String
)

Next, gets the result of the error class to the throwable instance with the deserializeHttpError extension like the below:

val result = pokemonService.fetchPokemonList()
result.onSuccessSuspend {
  Timber.d("fetched as Result: $it")
}.onFailureSuspend { throwable ->
  val errorBody = throwable.deserializeHttpError<ErrorMessage>()
}

Find this repository useful? โค๏ธ

Support it by joining stargazers for this repository. โญ
Also, follow me on GitHub for my next creations! ๐Ÿคฉ

License

Designed and developed by 2022 skydoves (Jaewoong Eum)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

retrofit-adapters's People

Contributors

rayworks avatar skydoves avatar xanscale avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

retrofit-adapters's Issues

List data not serialized

Please complete the following information:

  • Library Version [e.g. v1.0.1]
  • Affected Device(s): Google Pixel 4

Describe the Bug:
Api response with list, not serialized return empty list

Expected Behavior:
response with list returned to presentation layer

JSON

{
  "status": "OK",
  "code": "200",
  "data:": [
    {
      "id": "1ddb439d-30e8-4898-bd6f-6959e3a829ef",
      "name": "Indonesia",
      "prefix": "+62",
      "flagImageUrl": "https://img.geonames.org/flags/x/id.gif"
    },
    {
      "id": "65ab068a-002b-4571-863c-d08e03579901",
      "name": "United States",
      "prefix": "+1",
      "flagImageUrl": "https://img.geonames.org/flags/x/us.gif"
    }
  ],
  "meta": {
    "count": 2,
    "limit": 10,
    "offset": 0
  }
}

Implementation

Retrofit

                Retrofit.Builder()
                    .baseUrl(Secured.getBaseUrlApi())
                    .client(okHttpClient)
                    .addCallAdapterFactory(NetworkResponseAdapterFactory())
                    .addCallAdapterFactory(CoroutineCallAdapterFactory())
                    .addConverterFactory(ScalarsConverterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create(gson()))
                    .build()

Api Client

    @GET("country-codes/v1")
    suspend fun getCountryCode(
        @Query("limit") limit: Int,
        @Query("offset") offset: Int,
    ): Result<ApiPaging<CountryCodeData?>?>

data class ApiPaging<T>(
    @field:SerializedName("status") override val status: String? = null,
    @field:SerializedName("code") override val code: String? = null,
    @field:SerializedName("data") override val data: List<T?>? = null,
    @field:SerializedName("meta") override val meta: ApiPagingMeta? = null,
) : ApiResponseData<List<T?>?, ApiPagingMeta>

data class CountryCodeData(
    @field:SerializedName("prefix") val prefix: String? = null,
    @field:SerializedName("name") val name: String? = null,
    @field:SerializedName("id") val id: String? = null,
    @field:SerializedName("flagImageUrl") val flagImageUrl: String? = null
)

Data Repo

    override suspend fun getCountryCode(
        payload: CountryCodePayload
    ): Result<List<CountryCodeData?>?> {
        return api.getCountryCode(payload.limit, payload.offset).mapSuspend {
            it?.data //this always empty
        }
    }

Crash after R8/Proguard

Please complete the following information:

  • Library Version: 1.0.6
  • Affected Device(s) All

Describe the Bug:

After minify the code with R8, can't use the library anymore, and throw an exception:

java.lang.IllegalArgumentException: Unable to create call adapter for retrofit2.Call<arrow.core.Either>

I have used the proguard rules described in Retrofit2 repository:

-keepattributes Signature, InnerClasses, EnclosingMethod, Exceptions, *Annotation*, AnnotationDefault, RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations, InnerClasses, Annotation
-keepclassmembers,allowshrinking,allowobfuscation interface * {
    @retrofit2.http.* <methods>;
}
-dontwarn javax.annotation.**
-dontwarn kotlin.Unit
-dontnote retrofit2.Platform
-dontwarn retrofit2.KotlinExtensions
-dontwarn retrofit2.KotlinExtensions$*

-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>

-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface * extends <1>

-if interface * { @retrofit2.http.* public *** *(...); }
-keep,allowoptimization,allowshrinking,allowobfuscation class <3>

-keep,allowobfuscation,allowshrinking class retrofit2.Response
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
-keep,allowobfuscation,allowshrinking interface retrofit2.Call

According to the ArrowKt repository, it doesn't need extra rules in the proguard file.
Any idea of why this happen?

Expected Behavior:

It should work normally.

App Crash on HTTP 422 Unprocessable Entity

Please complete the following information:

  • Library Version [e.g. v1.0.0]
  • 1.0.5 (https://github.com/skydoves/retrofit-adapters/tree/main/retrofit-adapters-result)
  • Affected Device(s) [e.g. Samsung Galaxy s10 with Android 9.0]
    Xiaomi Android 10
    Describe the Bug:
    App Crashed on Http Exception 422 Unprocessable Entity
    Add a clear description about the problem.
    it is working with all other Exceptions and got crashed when we throw 422 error code
    exact line for crash i have added a two images

Screenshot 2023-03-15 at 2 04 57 PM

Screenshot 2023-03-15 at 2 06 56 PM

Expected Behavior:

A clear description of what you expected to happen.

Response headers

Is your feature request related to a problem?

Are you planning to expose the header fields of an HTTP response?

Describe the solution you'd like:

Not sure if this is possible with the Result type.

Describe alternatives you've considered:

One can use Call or retrofit2.Response as an alternative.

retrofit-adapters-result nullable body

i have a call that can return an optional body

Using normal retrofit all works correctly

@GET("....")
suspend fun getFoo(): Response<Foo>

but with this lib i receive KotlinNullPointerException on case of null body

@GET("....")
suspend fun getFoo(): Result<Foo?>

the problem are inside ResultExtension, why not just return body?

i made a PR #18

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.