Thursday 5 September 2019

Android Retrofit attempting to make incorrect request on first attempt

We are using retrofit to make API requests in our Android app and are running into a weird issue. I have a request that looks like this:

interface apiservice {

    @POST("user/login")
    fun postLogin(@Body body: LoginBody): LiveData<ApiResponseWrapper<LoginRemote>>

I make the request by doing:

apiservice.postLogin(
    ....
)


However, the first time this get's submitted, the request url is:

http://localhost/

First off, there isn't a single configuration in my app that sets the base url as localhost. Secondly, this request doesn't even have the /user/login appended to it.

This was working fine the last time we worked on this app a few weeks ago, but now it has this weird bug when we didn't even touch anything related to this...

EDIT

Here is my Retrofit builder

Retrofit.Builder()
    .baseUrl("${BuildConfig.API_URL}/en/api/v1/")
    .client(makeOkHttpClient(BuildConfig.DEBUG, sessionData))
    .addCallAdapterFactory(LiveDataCallAdapterFactory())
    .addConverterFactory(GsonConverterFactory.create(gson))
    .build()
    .create(YTPService::class.java)

Here is my Build Config in my :

buildConfigField("String", "API_URL", "\"https://myurl.org\"")

// BuildConfig's `DEBUG` gets set to FALSE when build occurs

And then this is what is printing out in the log files during this process:

// THE FIRST TIME I HIT SUBMIT AND TRIGGER THE REQUEST
D/LOGIN-RESPONSE: Response{protocol=http/1.1, code=500, message=Response.error(), url=http://localhost/}
    Error(errors=[com.mycompany.myproject.api.auth.login.LoginResponse$ErrorType$Generic@2148520])

... 

// THE SECOND TIME I HIT SUBMIT AND TRIGGER THE REQUEST IMMEDIATELY AFTER FIRST FAILED
D/LOGIN-RESPONSE: Response{protocol=http/1.1, code=200, message=Logged in as SampleUser1., url=https://myurl.org/en/api/v1/user/login}
    Ok(data=com.mycompany.myproject.api.auth.login.LoginResponse$SuccessData@4f03c75)


For even more details, check out this issue I opened on their repo

UPDATE

After looking at the logs on the server, I have confirmed that the proper request is actually being made, but retrofit's ApiResponseWrapper isn't setting the response with the proper information sent back from the server.

e.g.

apiservice.postLogin(LoginBody(username, password)).map { // it: ApiResponseWrapper<LoginRemote>

  // `it.response` == Response{protocol=http/1.1, code=500, message=Response.error(), url=http://localhost/}

  // When, in fact, the server actually sent back a 200 with the data I need. Retrofit just isn't handling it correct...

}

EDIT 2

Adding ApiResponseWrapper and LoginRemote classes as requested

ApiResponseWrapper

sealed class ApiResponseWrapper<R> {
    companion object {
        fun <R> success(response: Response<R>): ApiResponseWrapper.Success<R> =
                ApiResponseWrapper.Success(response)

        fun <T> failure(error: Throwable): ApiResponseWrapper.Failure<T> =
                ApiResponseWrapper.Failure(error)
    }

    class Failure<T>(val error: Throwable) : ApiResponseWrapper<T>()

    class Success<T>(val response: Response<T>) : ApiResponseWrapper<T>()
}

LoginRemote

class LoginRemote(@SerializedName("user") val user: User) {
    companion object {
        fun parseResponse(response: Response<LoginRemote>): LoginResponse {
            return if (response.isSuccessful) {
                Log.d("LOGIN-RESPONSE-BODY", response.body().toString())
                response.body()!!.format()
            } else if (response.code() == 302) {
                // accept terms again
                LoginResponse(listOf(LoginResponse.ErrorType.TermsRequired()))
            } else {
                val errorBody = response.errorBody()?.string()?.trim() ?: ""

                if (errorBody == "[\"Wrong username or password.\"]" || errorBody.contains("has not been activated or is blocked."))
                    return LoginResponse(listOf(LoginResponse.ErrorType.CredentialsInvalid()))
                if (errorBody.contains("[\"Already logged in as "))
                    return LoginResponse(LoginResponse.SuccessData(null, null, null))

                return LoginResponse(listOf(LoginResponse.ErrorType.Generic()))
            }
        }
    }

    fun format(): LoginResponse {
        Log.d("LOGIN-ROLE-KEYS", user.roles.toString())
        val roles = user.roles.keys
        val role = when {
            roles.contains(ROLE_ID_STANDARD) -> Role(ROLE_ID_STANDARD)
            roles.contains(ROLE_ID_LIMITED) -> Role(ROLE_ID_LIMITED)
            else -> Role(ROLE_ID_PREMIUM)
        }
        val countryOfResidence = if (user.countryOfResidence != null) user.countryOfResidence!!.und[0].value else "United Arab Emirates"
        return LoginResponse(LoginResponse.SuccessData(role = role, gender = user.fieldGender, countryOfResidence = countryOfResidence))
    }

    data class User(
            @SerializedName("field_gender") val fieldGender: Gender?,
            @SerializedName("roles") val roles: Map<Int, String>,
            @SerializedName("field_new_residence") val countryOfResidence: CountryOfResidence?
    )

    data class CountryOfResidence(
            @SerializedName("und") val und: List<CountryOfResidenceUND>
    )

    data class CountryOfResidenceUND(
            @SerializedName("value") val value: String
    )

}



from Android Retrofit attempting to make incorrect request on first attempt

No comments:

Post a Comment