Giter Site home page Giter Site logo

haroldadmin / networkresponseadapter Goto Github PK

View Code? Open in Web Editor NEW
552.0 7.0 60.0 1.26 MB

Retrofit call adapter to model success/failed responses as sealed types

Home Page: https://haroldadmin.github.io/NetworkResponseAdapter

License: Apache License 2.0

Kotlin 100.00%
kotlin retrofit kotlin-coroutines kotlin-test

networkresponseadapter's Introduction

NetworkResponse Retrofit adapter

Build Status

This library provides a Kotlin Coroutines based Retrofit call adapter for wrapping your API responses in a NetworkResponse sealed type.

Documentation

https://haroldadmin.github.io/NetworkResponseAdapter

Network Response

NetworkResponse<S, E> is a Kotlin sealed interface with the following states:

  • Success: Represents successful network calls (2xx response codes)
  • Error: Represents unsuccessful network calls
    • ServerError: Server errors (non 2xx responses)
    • NetworkError: IO Errors, connectivity problems
    • UnknownError: Any other errors, like serialization exceptions

It is generic on two types: a success response (S), and an error response (E).

  • S: Kotlin representation of a successful API response
  • E: Representation of an unsuccessful API response

Find complete documentation at https://haroldadmin.github.io/NetworkResponseAdapter.

Installation

Add the Jitpack repository to your list of repositories:

allprojects {
    repositories {
        maven { url 'https://jitpack.io' }
    }
}

And then add the dependency in your gradle file:

dependencies {
    implementation "com.github.haroldadmin:NetworkResponseAdapter:(latest-version)"
}

Release

License

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.

networkresponseadapter's People

Contributors

dependabot[bot] avatar garrytrue avatar haroldadmin 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

networkresponseadapter's Issues

V5.0

I opened an issue so we can discuss what changes can and or should be make in v5.0.

edit:
I wanted to label the “issue” but I can't

Error when app is builded in release mode

Hi!
I have this error while trying to use an API with my app:

E/c: java.lang.RuntimeException: Failed to invoke private l9.i() with no args
        at com.google.gson.internal.m.a(:5)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:3)
        at vf.c.b(:2)
        at vf.c.a(:1)
        at retrofit2.c0.f(:7)
        at retrofit2.y.a(:1)
        at zd.g.run(:7)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:923)
     Caused by: java.lang.InstantiationException: Can't instantiate abstract class l9.i
        at java.lang.reflect.Constructor.newInstance0(Native Method)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
        at com.google.gson.internal.m.a(:1)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:3) 
        at vf.c.b(:2) 
        at vf.c.a(:1) 
        at retrofit2.c0.f(:7) 
        at retrofit2.y.a(:1) 
        at zd.g.run(:7) 
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) 
        at java.lang.Thread.run(Thread.java:923) 

With mapping file l9.i() is com.haroldadmin.cnradapter.NetworkResponse -> l9.i

i m using the last stable AS arctic fox, with the latest Gradle plugin.
I don't have this error with Gradle 6.7.1 and kotlin 1.5.10

Unsuccessful responses don't return converted error object

Hi,

Just tested your adapter, but when calling a end-point which returns a 404 I'm not getting the converted error object back in the NetworkResponse.ServerError() (body is always null).

It looks like here https://github.com/haroldadmin/NetworkResponseAdapter/blob/master/src/main/kotlin/com/haroldadmin/cnradapter/NetworkResponseCall.kt#L22 it will always return a null body, and the actual error in Response.errorBody is ignored.

Also isn't it better to check if the Response is succesfull by using response.isSuccessful?

I think the following (lines 19-23 in NetworkResponseCall):

if (body != null) {
    callback.onResponse(this@NetworkResponseCall, Response.success(NetworkResponse.Success(body, response.headers())))
} else {
    callback.onResponse(this@NetworkResponseCall, Response.success(NetworkResponse.ServerError(null, response.code(), response.headers())))
}

could be replaced with something like this (note response.extractError doesn't exist yet) :

if(response.isSuccessful) {
    callback.onResponse(this@NetworkResponseCall,   Response.success(NetworkResponse.Success(body, response.headers())))
} else {
    callback.onResponse(this@NetworkResponseCall, Response.success(NetworkResponse.ServerError(response.extractError(errorBodyConverter), response.code(), response.headers())))
}

An example of a failed request:

<-- 404 https://api.themoviedb.org/4/discover/movies?sort_by=popularity.desc (126ms)
date: Fri, 18 Oct 2019 11:49:09 GMT
content-type: application/json;charset=utf-8
...
{"status_code":34,"status_message":"The resource you requested could not be found.","success":false}
<-- END HTTP (100-byte body)

And the logged response:

ServerError(body=null, code=404, headers=[snipped for brevity])

Application crashing when minifyEnabled is set to true

Probably caused by obfuscation. Currently in my rules I have mostly retrofit and okHttpt related rules, and my app crashing only when I'm trying to make http request. When I tried to comment http call useCase, everything works well.
What should I put in proguard-rules.pro?
Probably the same as this issue.

Compatibility with suspend functions

At this moment the library is working with deferred and normal functions but if you try to use the new Retrofit support for suspend functions without the deferred wrapper it is not working.

Adding NetworkResponseAdapterFactory to your Retrofit client in Java

Hey @haroldadmin!

First of all, sweet library. Makes things very standardized and thats a win.

Been using this in my side projects for a while now and its great! One of my project is in migration to Kotlin and codebase is in transition so there are some files/modules written in Java.

I was trying to add the NetworkResponseAdapterFactory to my RetrofitClient (written in Java) but unfortunately this doesn't seem to work. Throws an error saying incorrect type and even if I try casting the object, there is no luck. I was wondering if there is a workaround for this in Java for the time being.

Sample code for reference.

        return new Retrofit.Builder()
                .baseUrl(BuildConfig.BASE_URL)
                .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(NetworkResponseAdapterFactory())
                .client(httpClient) 

Wondering if there is a Java equivalent of adding the NetworkResponseAdapterFactory() as a call adapter factory.

Thanks 😄

moshi exception incorrectly classified as network error

When using mosh to parse invalid json, a com.squareup.moshi.JsonEncodingException might be produced.

Contrary to the readme and documentation, this is wrapped in a NetworkResponse.NetworkError rather than an UnknownError

Either update the docs or the code, but at least make clear what we can expect ;-)

5.0.0 is not backwards compatible

Hey @haroldadmin I use your library and unfortunately the 5.0.0 changes are not fully backwards compatible.

If you have tests that use the code on the NetworkResponse model then your tests will break (as mine did) and you'll have to replace code with an actual Response instance.

Example (for those who run into this):

Pre 5.0.0 code with mockito:

  • Success:
    whenever(apiService.signOut(any())).thenReturn(NetworkResponse.Success(code = 200, body = Unit))

  • Error:
    whenever(apiService.signOut(any())).thenReturn(NetworkResponse.ServerError(code = 500, body = ErrorResponse("boom")))

New 5.0.0 Implementation:

  • Success:
    whenever(apiService.signOut(any())).thenReturn(NetworkResponse.Success(response = Response.success(200), body = Unit))

  • Error:

val errorResponse = Response.error<String>(500, "{\"error\": \"boom\"}".toResponseBody("application/json".toMediaTypeOrNull()))
whenever(apiService.signOut(any())).thenReturn(NetworkResponse.ServerError(response = errorResponse, body = ErrorResponse("boom")))

Note: toResponseBody and toMediaTypeOrNull are extension methods built into okhttp.

It would be good to include a CHANGELOG.md file in the repo with these notes (or somewhere accessile and easily found - I loo for a CHANGELOG.md file personally)

How to handle responses with 200 **AND** 204 (No Content)

We can handle a 204 with

suspend fun updateStatusOnServer(): NetworkResponse<Unit, ErrorType>

But here the response can only be Unit. But what if I can have a 200 responses when there is new content and 204 when there is no new content?

Moshi parsing exceptions are swallowed

Moshi parsing exceptions are swallowed when data parsing fails.

The culprit is this line that rethrows exceptions instead of parsing them into unknown/generic errors:
https://github.com/haroldadmin/NetworkResponseAdapter/blob/master/src/main/kotlin/com/haroldadmin/cnradapter/ErrorExtraction.kt#L32

Strange enough, the exception is logged to logcat but wrapping the suspend function with a try/catch does not work, the exception is swallowed somewhere between this library and retrofit.

W/System.err: com.squareup.moshi.JsonDataException: Required value 'pointsReceived' missing at $
W/System.err:     at com.squareup.moshi.internal.Util.missingProperty(Util.java:605)
W/System.err:     at core.data.remote.model.Remote_LoyaltyUpgradeResponseJsonAdapter.fromJson(Remote_LoyaltyUpgradeResponseJsonAdapter.kt:44)
W/System.err:     at core.data.remote.model.Remote_LoyaltyUpgradeResponseJsonAdapter.fromJson(Remote_LoyaltyUpgradeResponseJsonAdapter.kt:17)
W/System.err:     at com.squareup.moshi.internal.NullSafeJsonAdapter.fromJson(NullSafeJsonAdapter.java:40)
W/System.err:     at retrofit2.converter.moshi.MoshiResponseBodyConverter.convert(MoshiResponseBodyConverter.java:45)
W/System.err:     at retrofit2.converter.moshi.MoshiResponseBodyConverter.convert(MoshiResponseBodyConverter.java:27)
W/System.err:     at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:225)
W/System.err:     at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:121)
W/System.err:     at okhttp3.RealCall$AsyncCall.run(RealCall.kt:140)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
W/System.err:     at java.lang.Thread.run(Thread.java:919)

NetworkResponseAdapterFactory crashes on unhandled response types

The check here: https://github.com/haroldadmin/NetworkResponseAdapter/blob/master/src/main/kotlin/com/haroldadmin/cnradapter/NetworkResponseAdapterFactory.kt#L14 will cause a crash if consumer uses other response types that can be handled by different factories but aren't parameterised (such as Completeable type with RxJava adapter). Retrofit's convention here is to return null when a factory realises it can't handle the response type so other Factories may try. This should therefore be the behaviour for unparameterised types as well as types that aren't NetworkResponse.

Workaround is to make sure NetworkResponseAdapterFactory is always the last adapter in the list but this shouldn't be required.

Code 302 (Found after redirect) results in UnknownError + No chance to get header and body of the response.

This is a dependent issue to:
square/okhttp#6456

Workaround: ResonseCodeInterceptor(302, 200)

/**
 * @author szsoftware
 */
class ResponseCodeInterceptor(private val from: Int, private val to: Int) : Interceptor {
    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val response = chain.proceed(request)
        val headers = response.headers
        val body = response.body

        val newCode = when(val oldCode = response.code) { from -> to else -> oldCode}

        return response
                .newBuilder()
                .code(newCode)
                .headers(headers)
                .body(body)
                .build()
    }
}

In my case I even only needed the headers, since the response body is empty (as expected).
In the responses header, in a Location "field" resides a token which was requested by a previous post request.
Due to sso mechanisms, a ridiculous 303 chain resulted in 302 at the end, what could finally not been processed by Retrofit + NetworkResponseAdapter.

When expression with Success and Error is not exhaustive

With the current implementation of NetworkResponse, the following expression would not compile:

fun isSuccess(networkResponse: NetworkResponse<*, *>) = when (networkResponse) {
    is NetworkResponse.Success -> true
    is NetworkResponse.Error -> false
}

If NetworkResponse was a sealed interface and Error was also a sealed interface that implemented NetworkResponse, the above expression would compile. I'd be happy to submit a pull request for this adjustment if I was accepted as a contributor.

How to use async Call?

Hello Harold. First of all, thank you for your job and this useful library.
Also, I want to use correctly the Call instance Because only it works correctly without runBlocking in Authentificator. Like in this case stack.
Please help me understand how it can be integrated or extended with your "pattern". Thank you!

How to manage offline response?

I'm trying to advise that the user is offline, but the adapter response always with UnknownError.
In this type of response i can0t access to the original raw respone, so I think it should be (in any error case) give to the caller.

Schermata 2020-04-04 alle 18 41 13

I think the problem is the conversion method,

val networkResponse = try { val convertedBody = errorConverter.convert(errorBody) NetworkResponse.ServerError(convertedBody, code, headers) } catch(ex: Exception) { NetworkResponse.UnknownError(ex) }

so the best way is to give the raw response int this way
val networkResponse = try {
val convertedBody = errorConverter.convert(errorBody)
NetworkResponse.ServerError(convertedBody, code, headers)
} catch(ex: Exception) {
NetworkResponse.UnknownError(ex, response = response)
}
Thanks and nice job

Response status lost when error body parse fail

When receiving an error response, if body parse fails we lose the reponse status code.
Since it is really a valid response, I think it is plausible to return as ServerError with empty body instead of UnknownError.

How To Handle Error Response ?

NetworkResponse<LoginResponse, ErrorResponse> . where to acess the ErrorResponse model i created
when (person) {
is NetworkResponse.Success -> {
// Handle successful response
}
is NetworkResponse.ServerError -> {
// Handle server error
}
is NetworkResponse.NetworkError -> {
// Handle network error
}
is NetworkResponse.UnknownError -> {
// Handle other errors
}

Simplify error parsing

I think HttpException.extractFromHttpException can return NetworkResponse.ServerError instead if NetworkResponse.

And one more, you have broken test in ErrorExtractionTest

If you allow me I can make a PR with fixes

[NF] Success with Empty Body (204) / Ignored Success Body

Hi @haroldadmin ,
First of all, amazing library!

I am working on a new feature to adapt it to my project and I believe can be useful for other people.

  • API requests can return 204 (no content) with an empty body and should be treated as a successful response.
  • Ignored body on success may occur in REST requests, just relying on the status code. However, I would still need to parse unsuccessful responses.

I intend to work on it in 1 week and I would like to know your concerns or any tips.

Excpetion: Unable to create call adapter when I try to use with Retrofit and Moshi

Hi,
I'm trying ot use your library with Moshi for parsing a json.
Here my models

@JsonClass(generateAdapter = true)
data class DataModel(
    @Json(name = "approfondimenti") val approfondimenti: List<Approfondimento>?,
    @Json(name = "glossari") val glossari: List<Glossario>?,
    @Json(name = "media") val media: List<Media>?,
    @Json(name = "percorsi") val percorsi: List<Percorso>?,
    @Json(name = "reperti") val reperti: List<Reperto>?,
    @Json(name = "sale") val sale: List<Sala>?,
    @Json(name = "servizi") val servizi: List<Servizio>?,
    @Json(name = "sezioni") val sezioni: List<Sezione>?
)

@Serializable
class ErrorResponse

Here the Retrofit interface for call API server

interface DataService {

    @GET("/data.json") suspend fun getData(): NetworkResponse<DataModel, ErrorResponse>
}

Finally the functions that returns the Retrofit service

fun makeRetrofitService(url: String): DataService {

        val okHttpClient: OkHttpClient =
            OkHttpClient.Builder()
                .readTimeout(30, TimeUnit.SECONDS)
                .build()

        return Retrofit.Builder()
            .baseUrl(url)
            .addCallAdapterFactory(NetworkResponseAdapterFactory())
            .addConverterFactory(MoshiConverterFactory.create())
            .build()
            .create(DataService::class.java)
    }

When I start the application I get this error: Exception Unable to create call adapter for retrofit2.Call<com.haroldadmin.cnradapter.NetworkResponse<it.inera.mnu.models.DataModel, it.inera.mnu.models.ErrorResponse>> for method DataService.getData

I spent a lot of time trying to understand a the problem without success. Could you help me?

NetworkResponse Error Managing

When i get a "204 No content" response from my API , NetWorkResponse consider it as a server error. But it should be a SUCCESS response. As describe in the ReadMe a response code 2xx is consider as successfull.

204 No Content count as ServerError

i am testing a loginApi, if user is not existed, it will return 204 no content. it supposed to be success response but it was fall under ServerError, do you know whats the issue here?

image

Unable to caught Json parsing Error

val response = repository.getReportById(id)
val x = when(response) {
is NetworkResponse.Success -> {
liveData.postValue(response.body)
//Json parsing Error not called here
}
is NetworkResponse.ServerError -> {
//Json parsing Error not called here
}
is NetworkResponse.NetworkError -> {
//Json parsing Error not called here
}
}

How to caught Json parsing Error if happens.

Publish kdocs on gh-pages or some other site

As a user of the library, I would like to see all of the documentation in an easy place such as github pages. This would allow me to see things not documented in the readme like the awesome support for backoff retries in the network response.

Since the library is open source, github actions could be used to do this automatically on push

NetworkResponse.ServerError: returns null message

API send response when there is any error, like below

{"message":"Missing Authentication Token"}

To handle an error, I have made below class

data class ErrorImages(@SerializedName("message") val message: String)

When I debug below code

is NetworkResponse.ServerError -> {
                    result?.let {
                        errorResponse.postValue(it.body)
                    }
                }

It always response like below

ServerError(body=ErrorImages(message=null), code=403, headers=date: Wed, 03 Feb 2021 04:37:37 GMT
content-type: application/json
content-length: 42
x-amzn-requestid: b500109b-6fca-4d8c-a9c5-f791b4a21f47
x-amzn-errortype: MissingAuthenticationTokenException
x-amz-apigw-id: aJtGxEpZSQ0Flfg=
)

I need to show message, but I am not understanding why message always null

Retrofit mock

How can i mock the response with retrofit mock? i did something like this but i get NetworkResponse$NetworkError cannot be cast to LoginResponseSuccess/Error

`
class MockService(val delegate: BehaviorDelegate) : Service {

override suspend fun login(body: LoginRequest):
        NetworkResponse<LoginResponseSuccess, LoginResponseError> {

    var response: NetworkResponse<LoginResponseSuccess, LoginResponseError>? = null

    if (body.username == "[email protected]") {
        response = NetworkResponse.ServerError<LoginResponseError>(
            getResponse(R.raw.login_response_error_401),
            code = 401
        )
    } else {
        response = NetworkResponse.Success<LoginResponseSuccess>(
            getResponse(R.raw.login_response_success),
            code = 200
        )
    }

    return delegate.returningResponse(response).login(body)
}

`

Empty body on certain server errors

I have some server errors that return empty bodies and the others return an error. Is there a way to handle this without returning a body on all my server errors ?

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.