Giter Site home page Giter Site logo

skydoves / sandwich Goto Github PK

View Code? Open in Web Editor NEW
1.4K 17.0 102.0 2.37 MB

🥪 Sandwich is an adaptable and lightweight sealed API library designed for handling API responses and exceptions in Kotlin for Retrofit, Ktor, and Kotlin Multiplatform.

Home Page: https://skydoves.github.io/sandwich/

License: Apache License 2.0

Kotlin 100.00%
android retrofit api network apiresponse datasource kotlin skydoves

sandwich's People

Contributors

5abhisheksaxena avatar chandroidx avatar danielpassos avatar goooler avatar johnjohndoe avatar renovate[bot] avatar rhkrwngud445 avatar skydoves 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sandwich's Issues

Parse non-standard json

Hello, how should I parse the data format like this [code:Int,data:T,error:String] through ApiResponse

Any Roadmap to Support Kotlin Multiplatform?

Is your feature request related to a problem?

No

Describe the solution you'd like:

Sample apps using KMM or KMP application with Hilt and ktor.

Describe alternatives you've considered:

Nil

[Help] Is it the best approach to validate responses?

First, thanks for the fantastic lib!

I would like to double-check that I'm not over-complicating things 😅
A pretty typical use case on my project is validating some fields in the successful response that might indicate abnormal behavior (some of them the app might know to handle)

Inside our repositories, we usually check some properties, and if everything is correct, we then map the network model to the UI model, otherwise will return an error result class wrapping it:

suspend fun doSomething(): MyResultWrapper<MyUIModel> {
        // We could get some error here and it will be handled by the VM
        val location = settingsStore.getLocation().dataOrNull() ?: return ApiResponse.Failure.Exception(
            IllegalArgumentException("Location should not be null")
        )

        return service.doSomething(request = location.id).let { response ->
            when {
                response is ApiResponse.Success && isInvalid(response.data) ->
                    ApiResponse.error(IllegalArgumentException("Some meaningful message"))
                else -> response
            }
        }.mapSuccess {
            toDomainModel()
        }
    }

Is that the correct way to handle that or am I missing something?

Thanks!!

Support usage in vanilla JVM projects

Is your feature request related to a problem?

I wanted to experiment with sandwich in a backend project, so a regular Kotlin/JVM project, not an Android one. Using the dependency declaration in the build file described in the readme results in resolve errors of the like

Execution failed for task ':common:compileKotlin'.
Error while evaluating property 'filteredArgumentsMap' of task ':common:compileKotlin'
Could not resolve all files for configuration ':common:compileClasspath'.
> Could not resolve com.github.skydoves:sandwich:1.2.7.
Required by:
project :common
> No matching variant of com.github.skydoves:sandwich:1.2.7 was found. The consumer was configured to find an API of a library compatible with Java 11, preferably in the form of class files, preferably optimized for standard JVMs, and its dependencies declared externally, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'jvm' but:
- Variant 'releaseApiElements-published' capability com.github.skydoves:sandwich:1.2.7 declares an API of a library:
- Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'jvm'

Describe the solution you'd like:

I would like to be able to resolve a variant of sandwich for a vanilla Kotlin/JVM project. I expect that the variant also has only the dependencies that are necessary for that variant - for example no dependency on android libraries or utilities.

Describe alternatives you've considered:

I found a closed issue: #4 where the user chose that moving everything to the android module is an alternative. However, I don't have an android module, so I don't see any alternatives.

Thanks for the great project!
Hannes

Do not working when requesting api using coroutine.

  • Library Version v1.0.5
  • Affected Device(s) Samsung Galaxy s10 with Android 10
var testLiveData: LiveData<String>? = null
init {
    testRequest()
}

fun testRequest() {
    Timber.w("testRequest")
    testLiveData = launchOnViewModelScope {
        this.loginRepository.test(
            onSuccess = {
                Timber.w("onSuccess")
            },
            onError = {
                Timber.w("onError : $it")
            }
        ).asLiveData()
    }
}

button.setOnClickListener {
    viewModel.testRequest()
}

init{} is working...
but button click is not working

what is problem??

crash on 401 unauthorized status

Please complete the following information:

  • Library Version [e.g. v1.0.0]
  • Affected Device(s) [e.g. Samsung Galaxy s10 with Android 9.0]

Describe the Bug:
when I send A user pass to my api which unauthorized and throws 401 status code. the apiresponse call adapter factory crashed.

--------- beginning of crash

2020-09-12 14:16:55.564 7771-7771/com.skydoves.pokedex E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.skydoves.pokedex, PID: 7771
retrofit2.HttpException: HTTP 401 Response.error()
at retrofit2.KotlinExtensions$await$2$2.onResponse(KotlinExtensions.kt:53)
at com.skydoves.sandwich.coroutines.ApiResponseCallDelegate$enqueueImpl$1.onResponse(ApiResponseCallDelegate.kt:33)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:161)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:504)
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:919)
Add a clear description about the problem.

Expected Behavior:

A clear description of what you expected to happen.

onError expected to be fired

Difference Exception Handling between Ktor and Retrofit

For Retrofit, In ApiResponseCallDelegate.kt
The part that waits for the API response is within a try-catch block

  override fun enqueueImpl(callback: Callback<ApiResponse<T>>) {
    coroutineScope.launch {
      try {
        val response = proxy.awaitResponse()
        val apiResponse = ApiResponse.responseOf { response }
        callback.onResponse(this@ApiResponseCallDelegate, Response.success(apiResponse))
      } catch (e: Exception) {
        callback.onResponse(
          this@ApiResponseCallDelegate,
          Response.success(ApiResponse.exception(e)),
        )
      }
    }
  }

But for Ktor, In HttpClientExtension.kt
'response' already holds requested HttpResponse.
Therefore, within the try-catch block in 'apiResponseOf', it's not possible to handle errors occur during the request process
(e.g. UnknownHostException)

public suspend inline fun <reified T> HttpClient.getApiResponse(
  builder: HttpRequestBuilder,
): ApiResponse<T> {
  builder.method = HttpMethod.Get
  val response = request(builder)
  return apiResponseOf { response }
}

It seems more fitting for the purpose to pass the function itself as a parameter rather than receiving the result of the request

java.lang.RuntimeException: Failed to invoke private com.skydoves.sandwich.ApiResponse() with no args

Please complete the following information:

  • Library Version 1.2.1
  • Affected Device(s) Pixel 4

Describe the Bug:
image
response that used Sandwich always failed, with exception like above

Expected Behavior:
Response should be success because implementation without Sandwich had no problem

Implementation:

@GET("api/v3/rekening/detail")
    suspend fun getRekeningDetail(
        @Header("Authorization") auth: String,
        @Query("id") id: String,
        @Query("type") type: String
    ): ApiResponse<ResponseResult<Rekening>>


data class ResponseResult<T>(
    @field:SerializedName("data")
    val data: T? = null,

    @field:SerializedName("message")
    val message: String? = null,

    @field:SerializedName("status")
    val status: Int? = null
)

fun getRekeningById(rekeningId: String, rekeningType: RekeningType) = flow {
        emit(State.Single.Loading())
        var rekeningData: Rekening? = null
        if (NetworkUtils.isConnected()) {
            val response =
                service.getRekeningDetail(
                    mSecuredPreferences.accessToken,
                    rekeningId,
                    rekeningType.value
                )
            response.suspendOnSuccess {
                dao.insertSyncData(listOf(data.data))
                rekeningData = dao.getRekeningById(rekeningId)
            }.suspendOnError {
                rekeningData = dao.getRekeningById(rekeningId)
            }.suspendOnException {
                rekeningData = dao.getRekeningById(rekeningId)
            }
        } else {
            rekeningData = dao.getRekeningById(rekeningId)
        }
        emit(State.Single.Success(rekeningData))
    }.flowOn(Dispatchers.IO)

On Failure should return the Failure Sealed class

Since 1.2.2 on failure returns the actual message for example

[ApiResponse.Failure.Error-BadRequest](errorResponse=Response{protocol=h2, code=400, message=, url=urlItriedtodosomethingWith})

It would be nice if this was the actual sealed class instead of the message. This would allow a user to just use onFailure and check the Exception/Error types themselves or do whatever with the actual sealed object. As it stands currently you lose information if it was an error, and logging from this string requires a bunch of parsing.

For example I have an extension function that logs based off the ApiResponse<*> type.

fun ApiResponse<*>.log(type: String) {
    when (this) {
        is ApiResponse.Failure.Exception -> {
            XLog.enableStackTrace(10).e("Exception $type ${this.message}", this.exception)
        }
        is ApiResponse.Failure.Error -> {
            XLog.e("error $type ${this.errorBody?.string()}")
            XLog.e("error response code ${this.statusCode.code}")
        }
        else -> {
            XLog.e("error $type")
        }
    }
}

I would like to do something like

  val loginResponseDto = authService.login(loginRequest).onFailure { 
                this.log("Trying to login")
            }

but currently have to do something like this

  val loginResponseDto = authService.login(loginRequest).onError{ 
                this.log("Trying to login")
            }.onException{ 
                this.log("Trying to login")
            }

[ASK] How can I get throw exception as onError/onFailure/onException in ViewModel ?

This is related to this question

In the Repository.

    suspend fun reCheckout(
        orderData: OrderData
    ) = flow{
        var jwt: String? = null

        loginAndRegister(orderData.phoneNumber)
            .suspendOnFailure { emit(this) }
            .suspendMapSuccess {
                val authData = this
                jwt = "jwt ${authData.auth.token}"
                Client.saveAuth(authData)
                cancelOrder(
                    orderData.orderId!!,
                    OrderCancelRequestBody("store", "re-checkout")
                ).getOrThrow()
            }
            .suspendOnFailure { emit(this) }
            .suspendMapSuccess { addMultiCartItem(jwt!!, orderData.multiCartBody!!).getOrThrow() }
            .suspendOnFailure { emit(this) }
            .suspendMapSuccess { checkOut(orderData.checkoutBody!!).getOrThrow() }
            .suspendOnFailure { emit(this) }
            .suspendOnSuccess { emit(this) }
    }

And

In the ViewModel that calls the Repository.

        repository.reCheckout(orderData!!).collectLatest { apiResponse ->
            apiResponse.onSuccess {
                try {
                    val checkoutResponse = data as CheckoutResponseBody
                    orderData!!.updateCheckoutResult(checkoutResponse)
                    _uiState.value = PaymentUiState.Success(orderData!!)
                }catch (e: Exception){
                    e.printStackTrace()
                    sendLog(e.stackTraceToString())
                }
            }.onError {
                sendLog("${this.errorBody?.string()}")
            }.onException {
                sendLog("$exception")
            }
        }

I am using in this way. And when one of the API calls gets error, it throws because it usesgetOrThrow().

public fun <T> ApiResponse<T>.getOrThrow(): T {
  when (this) {
    is ApiResponse.Success -> return data
    is ApiResponse.Failure.Error -> throw RuntimeException(message())
    is ApiResponse.Failure.Exception -> throw exception
  }
}

But I think that would be nice if I can get the response because checking the status code is important to check the server error.

I thought I could get the exception in the ViewModel. Is there anyway to approach this..?

So, I tried to do like this. But it's not working.

try {
    cancelOrder(
        orderData.orderId!!,
        OrderCancelRequestBody("store", "re-checkout")
    ).getOrThrow()
}catch (e: Exception){
    val result = Response.error(500, e.message?.toResponseBody(null))
    emit(ApiResponse.of { result })
}

I also need to dismiss like loading animation or something too.

Change parameter for map in ApiSuccessModelMapper

Sandwich exposes an interfaceApiSuccessModelMapper. The map parameter method should be named apiSuccessResponse instead of apiErrorResponse

package com.skydoves.sandwich

/**
 * @author skydoves (Jaewoong Eum)
 *
 * A mapper interface for mapping [ApiResponse.Success] response as a custom [V] instance model.
 *
 * @see [ApiSuccessModelMapper](https://github.com/skydoves/sandwich#apierrormodelmapper)
 */
public fun interface ApiSuccessModelMapper<T, V> {

  /**
   * maps the [ApiResponse.Success] to the [V] using the mapper.
   *
   * @param apiErrorResponse The [ApiResponse.Success] error response from the network request.
   * @return A custom [V] success response model.
   */
  public fun map(apiErrorResponse: ApiResponse.Success<T>): V
}

Describe the solution you'd like:

Rename map parameter apiErrorResponse into apiSuccessResponse.

Interface can't be instantiated! Register an InstanceCreator or a TypeAdapter for this type.

  • Library Version: 2.0.0
  • Tested on Samsung galaxy S21 FE 5G and Pixel 7 Pro API 33 emulator

Add a clear description about the problem.
In my retrofit service interface I have a get API call

@GET("/getSomeData/") suspend fun getData(): ApiResponse<List<<MyDataModel>>>
when I run the app after some time the app gets crashed with the below logs:

if I use Moshi Converter Factory -
java.lang.illegalArgumentException: Unable to create converter for com.skydoves.sandwich.ApiResponse<? extends java.util.List<com.packagename.MyDataModel>>

if I use Gson Converter Factory -
com.google.gson.JsonIOException: Interfaces can't be instantiated! Register an InstanceCreator or a TypeAdapter for this type. Interface name: com.skydoves.sandwich.ApiResponse

Expected Behavior:
The app should not crash.

[ASK] How to create sequential or parallel request?

right now i'm using CoroutinesResponseCallAdapterFactory, how to create sequential request and parallel request with sandwich?
Example for sequential = i have API for getConfig and getBanner, frist getConfig, when succeed it will call getBanner
Example for parallel = API getProductDetail, getUserLimit. both API will parralel then result of 2 api will combine into 1 result

thank you

runAndRetry not working when type of ApiResponse had type variable

hello @skydoves, i had a case like this. the endpoint return the json with template like @dempsey. my problem is when I created the base class for the response it's working. but when I'm trying to use runAndRetry extension, it's show the warning :
Type mismatch. Required: Any Found: BaseResponse<TokenResponseModel>?

Screenshot 2023-12-01 135918

but when i change the body without the BaseResponse model, the 'runAndRetry ' method working well without the warning show up.

how do i solve this?

this is my base response model class
@Keep @Serializable data class BaseResponse<T>( @SerializedName("error") val error: ErrorModel? = null, @SerializedName("success") val success: Boolean?, @SerializedName("data") val data: T? = null )

Originally posted by @ldileh in #30 (comment)

Couldn't resolve ApiResponseCallAdapter class

사용 버전: implementation("com.github.skydoves:sandwich:2.0.6")

문제 현상:
retrofit의 addConverterFactory(ApiResponseCallAdapterFactory.create()) 코드를 추가하려고 했으나
ApiResponseCallAdapterFactory를 인식하지 못함

addConverterFactory 없이 코드를 실행시키면 아래 에러가 남
java.lang.IllegalArgumentException: Unable to create converter for com.skydoves.sandwich.ApiResponse

�dependency 추가된 파일을 보면 ApiResponseCallAdapterFactory 모듈인 refrofit이 존재하지 않음
image

라이브러리 사용을 잘못 한 것인지 아니면 이슈가 있는지 확인 부탁드립니다.

Unresolved reference when adding dependency to a Kotlin module

Hi @skydoves, and thanks for this nice library

When adding this dependency to the build.gradle file of an Android module, it works fine.
But doing the same in the build.gradle of a pure Kotlin module, I'm not able to see the library (Unresolved reference). The IDE suggests "Add library 'Gradle: com.github.skydoves:sandwich:1.0.4@aar' to classpath"; and even doing that doesn't solve the problem.
This is my build.gradle file:

apply plugin: 'kotlin'

dependencies {
    // some other dependencies
    implementation "com.github.skydoves:sandwich:1.0.4"
}

Could you explain how to handle this issue?
Thanks

Not nullable body on success

In ApiResponse.Success the data property is nullable. And i understand why it is - it's just a wrapper around retrofit response logic. But, when i'm using clean suspend fun something(): Type i will get an exception if the returned type is null. Which is good, i do expect that!

If i wanted null to be possible in my response - i would use a nullable type as a generic parameter. If i don't - my assumption is that in case of success it will always be properly created. The need for another null check does make the whole Success type pointless in my opinion. Because i'm still not sure if it really was a success, i have to do another check..

Have you considered handling that?

[ASK] Is there any feature that is equivalent to Single in sandwich?

I need to call sequential APIs. Is there any way to make it simple?

It's maybe related to 'map' feature or this one

    suspend fun reCheckout(
        orderData: OrderData
    ) = flow {
        loginAndRegister(orderData.phoneNumber).suspendOnSuccess {
            val jwt = "jwt ${data.auth.token}"
            Client.saveAuth(data)
            cancelOrder(
                orderData.orderId,
                OrderCancelRequestBody("store", "re-checkout")
            ).suspendOnSuccess {
                addMultiCartItem(jwt, orderData.cartItems).suspendOnSuccess {
                    checkOut(orderData.checkoutBody!!).suspendOnSuccess {
                        emit(this.data)
                    }.suspendOnError {
                        emit(this)
                    }.suspendOnException {
                        emit(this)
                    }
                }.suspendOnError {
                    emit(this)
                }.suspendOnException {
                    emit(this)
                }
            }.suspendOnError {
                emit(this)
            }.suspendOnException {
                emit(this)
            }
        }.suspendOnError {
            emit(this)
        }.suspendOnException {
            emit(this)
        }
    }

I don't know if it's the right way or not. I'd like to have only one onError and onException here like Single in RxJava. Is that possible?

I also want to handle the data in ViewModel with onSuccess, onException, onError, etc. How can I implement it?

Global ApiResponseFailureMapper not returning desired results

First of all, thank you for creating this awesome library :)

Please complete the following information:

  • Library Version 2.0.5

Describe the Bug:
The library allows us to create custom error mappers and assign them globally, but because of the library internals the mapping result is not really returned to the app. Example below

Some dummy classes for failures:

class SandwichException(throwable: Throwable) : ApiResponse.Failure.Exception(throwable)
class SandwichError(payload: Any?) : ApiResponse.Failure.Error(payload)

and a dummy global mapper:

class ApiFailureMapper : ApiResponseFailureMapper {

    override fun map(apiResponse: ApiResponse.Failure<*>): ApiResponse.Failure<*> {
        return when (apiResponse) {
            is ApiResponse.Failure.Exception -> SandwichException(apiResponse.throwable)

            is ApiResponse.Failure.Error -> SandwichError(apiResponse.payload)
        }
    }
}

SandwichInitializer.sandwichFailureMappers.add(ApiFailureMapper())

Now when we face an exception, like IOExceptions, we can expect an instance of SandwitchException with IOException throwable, right? But instead we receive the original ApiResponse.Failure.Exception.

I did some investigation and I belive the error is caused by

public fun exception(ex: Throwable): Failure.Exception =
Failure.Exception(ex).apply { operate().maps() }

because apply will trigger lambda (operators and mappers) as expected, but the result is not assigned anywhere and the original Failure.Exception is returned.

Expected Behavior:

When using global operators and mappers the app should properly return the modified Failure.Exception instances

Why is this error not intercepted

Please complete the following information:

  •          sandwich:1.0.4
    
  • Affected Device(s) [e.g. Samsung Galaxy s10 with Android 9.0]
    compileSdkVersion: 30,
    buildToolsVersion: "29.0.3",
    Describe the Bug:
    retrofit2.HttpException: HTTP 400 Response.error()
    at retrofit2.KotlinExtensions$await$2$2.onResponse(KotlinExtensions.kt:53)
    at com.skydoves.sandwich.coroutines.ApiResponseCallDelegate$enqueueImpl$1.onResponse(ApiResponseCallDelegate.kt:33)
    at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:161)
    at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
    at java.lang.Thread.run(Thread.java:818)
    Add a clear description about the problem.

Expected Behavior:
net: <-- 400 http://******.com/auth.json (165ms)
net: Server: nginx/1.11.6
net: Date: Fri, 12 Nov 2021 01:20:42 GMT
net: Content-Type: application/json;char
net: Transfer-Encoding: chunked
net: Connection: keep-alive
net: {"code":11015,"message":"无效的客户端"}

ApiResponseCallAdapterFactory is missing in 2.0.4

2.0.4
Android 12

Can't import ApiResponseCallAdapterFactory

import com.skydoves.sandwich.adapters.ApiResponseCallAdapterFactory
...
.addCallAdapterFactory(ApiResponseCallAdapterFactory.create()) 

image

image

How to get custom error from my server?

When i call api to my server and handle error, i want to get my custom error message like this
}
"statusCode": 400,
"message": "Registered phone number"
}

But when i use sandwich lib, error message convert to [ApiResponse.Failure.Error-BadRequest](errorResponse=Response{protocol=http/1.1, code=400, message=Bad Request
I just want to get my message error "Registered phone number"

Can I use Sandwich with Mongodb Realm API

Is your feature request related to a problem?

I can't find any references and examples on how to use Sandwich with Mongodb Realm API access.

Describe the solution you'd like:

Documentation about if Sandwich is only available for HTTP and REST API. If applicable please provide instruction on how to use sandwich to connect Realm API with samples.

Describe alternatives you've considered:

Manual exception control or backend server with Ktor(which I want to avoid)

EmptyBodyInterceptor doesn't work with GsonConverterFactory

Please complete the following information:

  • Library Version v1.3.4

Describe the Bug:
When usingEmptyBodyInterceptor to enable nullable bodies I get an exception

OFException: End of input at line 1 column 1 path $
    at com.google.gson.stream.JsonReader.nextNonWhitespace(JsonReader.java:1421)
    at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:549)
    at com.google.gson.stream.JsonReader.peek(JsonReader.java:425)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:207)
    at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:42)
...

Issue is caused by GsonConverterFactory because "" is not a valid Json.
google/gson#330

Expected Behavior:
No exception is thrown when using EmptyBodyInterceptor with GsonConverterFactory

Cannot inline bytecode built with JVM target 11 into bytecode that is being built with JVM target 1.8. Please specify proper '-jvm-target' option

  • Library Version 1.3.9
  • Affected Device(s) [e.g. Samsung Galaxy s10 with Android 9.0]

Describe the Bug:
Cannot inline bytecode built with JVM target 11 into bytecode that is being built with JVM target 1.8. Please specify proper '-jvm-target' option

My gradle.build has the following
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}

the gradle JDK version is 17

Add a clear description about the problem.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

  • chore(deps): update ktorfit to v1.14.0 (de.jensklingenberg.ktorfit, de.jensklingenberg.ktorfit:ktorfit-ksp, de.jensklingenberg.ktorfit:ktorfit-lib-light, de.jensklingenberg.ktorfit:ktorfit-lib)

Detected dependencies

github-actions
.github/workflows/build.yml
  • actions/checkout v4
  • actions/setup-java v4
  • actions/checkout v4
  • actions/setup-java v4
  • actions/checkout v4
  • actions/setup-java v4
  • actions/cache v4
.github/workflows/publish-docs.yml
  • actions/checkout v4@1d96c772d19495a3b5c517cd2bc0cb401ea0529f
  • actions/setup-python v5
  • actions/cache v4
.github/workflows/publish-snapshot.yml
  • actions/checkout v4.1.3@1d96c772d19495a3b5c517cd2bc0cb401ea0529f
  • actions/setup-java v4.2.1
.github/workflows/publish.yml
  • actions/checkout v4.1.3@1d96c772d19495a3b5c517cd2bc0cb401ea0529f
  • actions/setup-java v4.2.1
gradle
buildSrc/src/main/kotlin/com/github/skydoves/sandwich/Configuration.kt
gradle.properties
settings.gradle.kts
build.gradle.kts
app/build.gradle.kts
  • androidx.multidex:multidex 2.0.1
baselineprofile/build.gradle.kts
buildSrc/build.gradle.kts
gradle/libs.versions.toml
  • androidx.appcompat:appcompat 1.6.1
  • com.google.android.material:material 1.11.0
  • com.squareup.retrofit2:retrofit 2.11.0
  • com.squareup.retrofit2:converter-moshi 2.11.0
  • com.squareup.okhttp3:okhttp 4.12.0
  • com.squareup.okio:okio 3.9.0
  • io.ktor:ktor-client-core 2.3.10
  • io.ktor:ktor-client-content-negotiation 2.3.10
  • io.ktor:ktor-serialization-kotlinx-json 2.3.10
  • io.ktor:ktor-client-okhttp 2.3.10
  • io.ktor:ktor-client-darwin 2.3.10
  • de.jensklingenberg.ktorfit:ktorfit-lib 1.13.0
  • de.jensklingenberg.ktorfit:ktorfit-lib-light 1.13.0
  • de.jensklingenberg.ktorfit:ktorfit-ksp 1.13.0
  • org.jetbrains.kotlinx:kotlinx-coroutines-core 1.8.0
  • org.jetbrains.kotlinx:kotlinx-serialization-json 1.6.3
  • com.squareup.moshi:moshi-kotlin 1.15.1
  • com.squareup.moshi:moshi-kotlin-codegen 1.15.1
  • com.github.bumptech.glide:glide 4.16.0
  • androidx.lifecycle:lifecycle-livedata-ktx 2.7.0
  • androidx.lifecycle:lifecycle-viewmodel-ktx 2.7.0
  • com.jakewharton.timber:timber 5.0.1
  • androidx.arch.core:core-testing 2.2.0
  • org.mockito:mockito-core 5.11.0
  • org.mockito:mockito-inline 5.11.0
  • com.nhaarman.mockitokotlin2:mockito-kotlin 2.2.0
  • com.squareup.okhttp3:mockwebserver 4.12.0
  • org.jetbrains.kotlinx:kotlinx-coroutines-test 1.8.0
  • junit:junit 4.13.2
  • androidx.benchmark:benchmark-macro-junit4 1.2.4
  • androidx.test:runner 1.5.2
  • androidx.test:rules 1.5.2
  • androidx.test.ext:junit-ktx 1.1.5
  • androidx.profileinstaller:profileinstaller 1.3.1
  • androidx.test.uiautomator:uiautomator 2.3.0
  • androidx.core:core-ktx 1.13.0
  • androidx.test.ext:junit 1.1.5
  • androidx.test.espresso:espresso-core 3.5.1
  • com.android.application 8.3.2
  • com.android.library 8.3.2
  • org.jetbrains.kotlin.android 1.9.23
  • org.jetbrains.kotlin.multiplatform 1.9.23
  • org.jetbrains.kotlin.plugin.serialization 1.9.23
  • org.jetbrains.kotlin.kapt 1.9.23
  • com.google.devtools.ksp 1.9.23-1.0.20
  • de.jensklingenberg.ktorfit 1.13.0
  • org.jetbrains.dokka 1.9.20
  • androidx.baselineprofile 1.2.4
  • com.diffplug.spotless 6.25.0
  • org.jetbrains.kotlinx.binary-compatibility-validator 0.14.0
  • com.android.test 8.3.2
sandwich/gradle.properties
sandwich/build.gradle.kts
sandwich-ktor/gradle.properties
sandwich-ktor/build.gradle.kts
sandwich-ktorfit/gradle.properties
sandwich-ktorfit/build.gradle.kts
sandwich-retrofit/build.gradle.kts
sandwich-retrofit-datasource/build.gradle.kts
sandwich-retrofit-serialization/build.gradle.kts
scripts/publish-module.gradle.kts
gradle-wrapper
gradle/wrapper/gradle-wrapper.properties
  • gradle 8.7

  • Check this box to trigger a request for Renovate to run again on this repository

java.lang.NullPointerException:::sandwich.ApiResponse$Failure$Error.toString

--sandwich 1.3.5
--okhttp 4.10.0
--okio-jvm 3.0.0
--Xiao Mi Note3 (Android 9,API 28)

java.lang.NullPointerException
at okio.Segment.pop(Segment.kt:95)
at okio.Buffer.readString(Buffer.kt:321)
at okio.Buffer.readString(Buffer.kt:302)
at okhttp3.ResponseBody.string(ResponseBody.kt:187)
at com.skydoves.sandwich.ApiResponse$Failure$Error.toString(ApiResponse.kt:82)
at com.skydoves.sandwich.ResponseTransformer__ResponseTransformerKt.message(ResponseTransformer.kt:759)
at com.skydoves.sandwich.ResponseTransformer.message(ResponseTransformer.kt:1)
at com.robertsi.httpresultcope.mapper.ErrorResponseMapper.map(ErrorResponseMapper.kt:37)
at com.robertsi.httpresultcope.mapper.ErrorResponseMapper.map(ErrorResponseMapper.kt:28)
at com.robertsi.httpresultcope.repository.HttpCommonRepository$getManual$1.invokeSuspend(HttpCommonRepository.kt:197)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:42)
at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@7db45e5, Dispatchers.IO]


Thank you for your open source Sandwich Project
I am currently using and found this error. I hope you can capture and handle this exception

After enable R8 full mode getting ParameterizedType error

Please complete the following information:

  • Library Version: 1.3.5

Describe the Bug:

This issue is similar to the issue in Retrofit (square/retrofit#3751) but I'm not sure, but I've duplicated it here. This happens with AGP 8.0. As a solution, it's proposed to add these proguard lines to the project (square/retrofit#3751 (comment)):

 # Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items). 
 -keep,allowobfuscation,allowshrinking interface retrofit2.Call 
 -keep,allowobfuscation,allowshrinking class retrofit2.Response 
  
 # With R8 full mode generic signatures are stripped for classes that are not 
 # kept. Suspend functions are wrapped in continuations where the type argument 
 # is used. 
 -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation 

but it doesn't actually work, After adding this proguard lines I get a slightly different error:

 java.lang.IllegalArgumentException: Unable to create call adapter for retrofit2.Call<com.skydoves.sandwich.ApiResponse>
                     for method ComicVineService.issues
                 	at retrofit2.Utils.methodError(SourceFile:47)
                 	at retrofit2.HttpServiceMethod.parseAnnotations(SourceFile:394)
                 	at retrofit2.Retrofit.loadServiceMethod(SourceFile:31)
                 	at retrofit2.Retrofit$1.invoke(SourceFile:45)
                 	at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
                 	at $Proxy9.issues(Unknown Source)
                        ...
Caused by: java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
                 	at com.skydoves.sandwich.adapters.ApiResponseCallAdapterFactory.get(SourceFile:53)
                 	at retrofit2.Retrofit.callAdapter(SourceFile:33)
                 	at retrofit2.HttpServiceMethod.parseAnnotations(SourceFile:375)
                 	... 41 more
 @GET("issues?format=$FORMAT&field_list=${Fields.Issues}")
    @JvmSuppressWildcards
    suspend fun issues(
        @Query("api_key") apiKey: String,
        @Query("offset") offset: Int,
        @Query("limit") limit: Int,
        @Query("sort", encoded = true) sort: ComicVineSort?,
        @Query("filter[]", encoded = true) filter: List<ComicVineFilter>?,
    ): ApiResponse<IssuesResponse>

Issue with the ApiResponse.Failure<*>

I have the following sample code and have my internet turned off. So an unresolved host is thrown

  val loginResponse = authService.login(loginRequest)

            loginResponse.onFailure {
                Timber.d("This Failed")
            }
            Timber.d("loginResponse is Failure ${loginResponse is ApiResponse.Failure<*>}}")
            Timber.d("loginResponse is Exception ${loginResponse is ApiResponse.Failure.Exception<*>}}")

Based off the comments and the types in onFailure, both these timber log statements should be true and This Failed should print. Since the comments for onFailure say "A function that would be executed for handling error responses if the request failed or get an exception."

How to retry request?

in readme section about coroutine, it will call like this

  init {
    posterListLiveData = liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
      emitSource(disneyService.fetchDisneyPosterList()
        .onSuccess {
          // stub success case
          livedata.post(response.data)
        }.onError {
          // stub error case
        }.onException {
          // stub exception case
        }.toLiveData()) // returns an observable LiveData
    }
  }

when onError is trigger, i show a snackbar, and have button retry, but how to retry the request again?

Crash on HTTP 403 when api type is Call<T>

Please complete the following information:

  • Library Version: v1.0.8
  • Affected Device(s): Pixel 3a with Android 11

Describe the Bug:

The problem looks similar with crash on 401 unauthorized status #5

Crash log:

2021-01-04 18:14:25.391 14244-14534/net.hikingbook.hikingbook E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-2
    Process: net.hikingbook.hikingbook, PID: 14244
    retrofit2.HttpException: HTTP 403 
        at retrofit2.KotlinExtensions$await$2$2.onResponse(KotlinExtensions.kt:53)
        at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:161)
        at com.google.firebase.perf.network.InstrumentOkHttpEnqueueCallback.onResponse(InstrumentOkHttpEnqueueCallback.java:69)
        at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
        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)

Here is my code. I try to use the Call to get the response.

Service:

@POST("/login")
    suspend fun postLogin(
            @Body body: RequestBody
    ): Call<User>

Repository:

   suspend fun login(email: String, password: String) {
        val params: MutableMap<String, String> = mutableMapOf(
                "email" to email,
                "password" to password
        )
        router?.let {
            val request = it.postLogin(body = createRequestBody(params))
            request.callback(
                    onSuccess = { user ->
                        handleLoginResponse(user = user)
                    },
                    onError = {}
            )
        }
    }

   suspend fun <T> Call<T>.callback(
            onSuccess: (T?) -> Unit,
            onError: (String) -> Unit
    ) {
        this.request { response ->
            runBlocking {
                response.suspendOnSuccess {
                    onSuccess(data)
                }.suspendOnError {
                    when (statusCode) {
                        StatusCode.Forbidden -> {
                            // do somting...
                        }
                        else -> {
                            onError(message())
                        }
                    }
                }.suspendOnException {
                    onError(message())
                }
            }
        }
    }

Expected Behavior:
If the api is type is ApiResponse then the suspendOnError() method can handle the 403. But Call cannot handle
by onError.
Am I do something wrong? Thanks~

[ASK] How can I handle 200 but error case?

Some part of my app use Google Spreadsheet.

And I implemented REST API there with Apps Script.

I think this is rare case but, google server gives me 200 with html code when it has server error.

So, what I am trying is getting the response with ApiResponse. And then try to convert the response to SpreadSheetResponseBody which is a data class when it's successful.

This is my concept.

    suspend fun updateDashboard(
        ssId: String,
        method: String,
        device: String,
        isTest: Boolean,
        request: KioskDashboardReqBody
    ): ApiResponse<SpreadSheetResponseBody>{
        val result = service.updateDashboard(ssId, method, device, isTest, request).onSuccess {
            try {
                val data = Gson().fromJson(this.data, SpreadSheetResponseBody::class.java)
                return ApiResponse.Success<SpreadSheetResponseBody>(data)
            }catch(e: Exception){
                return ApiResponse.Failure<SpreadSheetResponseBody>(e)
            }
        }
        return ApiResponse.Failure<SpreadSheetResponseBody>("Server Error")
    }

This code is invalid.
Since google gives me 200. I'd like to create ApiResponse instead in the Repository.

Is this possible to handling like this?

deserializeErrorBody() throws internal exceptions

Please complete the following information:

  • Library Version 2.0.5
  • Xiaomi 11T Pro

Describe the Bug:

deserializeErrorBody() extenstion throws all internal exceptions, for example in case of any decoding-specific error - SerializationException if @serializable data class does not match with backend answer.

Expected Behavior:

deserializeErrorBody returns null if the error body is empty. Maybe add try catch on internal json.decodeFromString() to catch serialization exceptions and return null in case of this exception.
I can create pull request with such implementation

Why is this error not intercepted

Please complete the following information:

  •          sandwich:1.0.4
    
  • Affected Device(s) [e.g. Samsung Galaxy s10 with Android 9.0]
    compileSdkVersion: 30,
    buildToolsVersion: "29.0.3",
    Describe the Bug:
    retrofit2.HttpException: HTTP 400 Response.error()
    at retrofit2.KotlinExtensions$await$2$2.onResponse(KotlinExtensions.kt:53)
    at com.skydoves.sandwich.coroutines.ApiResponseCallDelegate$enqueueImpl$1.onResponse(ApiResponseCallDelegate.kt:33)
    at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:161)
    at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
    at java.lang.Thread.run(Thread.java:818)
    Add a clear description about the problem.

Expected Behavior:
net: <-- 400 http://******.com/auth.json (165ms)
net: Server: nginx/1.11.6
net: Date: Fri, 12 Nov 2021 01:20:42 GMT
net: Content-Type: application/json;char
net: Transfer-Encoding: chunked
net: X-Application-Context: ydt-api-gate
net: Connection: keep-alive
net: {"code":11015,"message":"无效的客户端"}

How to get error message when onError

    data class Error<T>(val response: Response<T>) : ApiResponse<T>() {
      val statusCode: StatusCode = getStatusCodeFromResponse(response)
      val headers: Headers = response.headers()
      val raw: okhttp3.Response = response.raw()
      val errorBody: ResponseBody? = response.errorBody()
      override fun toString(): String = "[ApiResponse.Failure.Error-$statusCode](errorResponse=$response)"
    }

how to get error message? because when i used apiResponse.message() it will return like in toString() function. what i want just message only. thank you

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.