jamian
jamian

Reputation: 1652

Add Header Parameter in Retrofit

I'm trying to call an API which requires me to pass in an API key.

My Service call using HttpURLConnection is working perfectly.

url = new URL("https://developers.zomato.com/api/v2.1/search?entity_id=3&entity_type=city&q=" + params[0]);
urlConnection = (HttpURLConnection) url.openConnection();

urlConnection.setRequestProperty("user-key","9900a9720d31dfd5fdb4352700c");

if (urlConnection.getResponseCode() != 200) {
    Toast.makeText(con, "url connection response not 200 | " + urlConnection.getResponseCode(), Toast.LENGTH_SHORT).show();
    Log.d("jamian", "url connection response not 200 | " + urlConnection.getResponseCode());
    throw new RuntimeException("Failed : HTTP error code : " + urlConnection.getResponseCode());
}

However, I'm not sure how this works with Retrofit as my call in going into Failure at all times. Here's the code I'm using for the same service call

@GET("search")
Call<String> getRestaurantsBySearch(@Query("entity_id") String entity_id, @Query("entity_type") String entity_type, @Query("q") String query,@Header("Accept") String accept, @Header("user-key") String userkey);

and I'm using this to call it

Call<String> call = endpoint.getRestaurantsBySearch("3","city","mumbai","application/json","9900a9720d31dfd5fdb4352700c");

All these calls are going into the OnFailure Method in RetroFit. If I send it without the HeaderParameters it goes into Success with a 403 because I obviously need to pass the api key somewhere but I cant figure out how.

@GET("search")
Call<String> getRestaurantsBySearch(@Query("entity_id") String entity_id, @Query("entity_type") String entity_type, @Query("q") String query);

The error I'm getting in OnFailure is

java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 2 path $

Upvotes: 82

Views: 199701

Answers (6)

Tonnie
Tonnie

Reputation: 8112

Let me also comment a little bit (actually a lot) about adding headers in Kotlin focusing on Dependency Injection.

The best approach would be to provide both OkHttpClient and HttpLoggingInterceptor on the same di method making use of the handy Kotlin Scoping function in this case also and apply.

We will be needing these Retrofit (2.9) and OkHttpClient dependencies - this example uses Kotlin DSL but should be more or less the same in Groovy. Of-course you will be needing other dependencies like Hilt if you are using Dependency Injection.

implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.7")
implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.7")

Next stop is to create the @Provide function which returns OkHttpClient.

@Provides
@Singleton
fun provideOkHttpClient():OkHttpClient { ...}

Background theory about Interceptors is very vital; to use an interceptor, you need to create a class that implements the Interceptor interface and override the intercept() method.

intercept() receives an Interceptor.Chain object - which represents the current request and allows you to proceed with the request by calling the proceed() method, or cancel the request by throwing an exception. intercept() override function returns a Response object which is exactly what chain.proceed(request) returns.

class MyInterceptor : Interceptor {
    //throw an exception to cancel request
    @Throws(IOException::class)

    override fun intercept(chain: Interceptor.Chain): Response {

        val request = chain.request()
                .newBuilder() // returns Request.Builder
                .addHeader("Header_1", "value_1")
                .build()

        //proceed with the request
        return chain.proceed(request)
    }

}

Thanks to Kotlin Anonymous Function Syntax and Builder Pattern we can skip the above theory steps and start to build OkHttpClient which has the addInterceptor() function.

fun provideOkHttpClient(): OkHttpClient {
    
            //build client
            return OkHttpClient.Builder()
    
                    //create anonymous interceptor in the lambda and override intercept
                    // passing in Interceptor.Chain parameter
                    .addInterceptor { chain ->
                      //return response
                        chain.proceed(
                              //create request
                                chain.request()
                                        .newBuilder()
                                       //add headers to the request builder
                                        .also {
                                            it.addHeader("Header_1", "value_1")
                                            it.addHeader("Header_2", "value_2")
                                        }
                                        .build()
                        )
                    }
                    .also { okHttpClient ->.... }

In the above code addInterceptor() opens up a lambda where we anonymously override intercept() passing in a chain parameter.

We use chain.proceed(request) to return a Response. It is when constructing the request to pass to chain.proceed() that we modify the actual request to add the headers.

You can also proceed to build up on the OkHttpClient to add timeouts etc.

.also { okHttpClient ->

                    okHttpClient.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
                    okHttpClient.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)

                    if (BuildConfig.DEBUG) {
                        val httpLoggingInterceptor = HttpLoggingInterceptor().apply {

                            level = HttpLoggingInterceptor.Level.BODY
                        }

                        okHttpClient.addInterceptor(httpLoggingInterceptor)
                    }
                }
                .build()

This is the final code.

@Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient {

        //build client
        return OkHttpClient.Builder()

                //create anonymous interceptor in the lambda and override intercept
                // passing in Interceptor.Chain parameter
                .addInterceptor { chain ->

                    //return response
                    chain.proceed(
                            //create request
                            chain.request()
                                    .newBuilder()

                                    //add headers to the request builder
                                    .also {
                                        it.addHeader("Header_1", "value_1")
                                        it.addHeader("Header_2", "value_2")
                                    }.build()

                    )
                }
                //add timeouts, logging
                .also { okHttpClient ->

                    okHttpClient.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
                    okHttpClient.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
                        //log if in debugging phase
                    if (BuildConfig.DEBUG) {
                        val httpLoggingInterceptor = HttpLoggingInterceptor().apply {

                            level = HttpLoggingInterceptor.Level.BODY
                        }

                        okHttpClient.addInterceptor(httpLoggingInterceptor)
                    }
                }
                .build()


    }

This marks my StackOverflow's longest ever post, I'm sorry guys.

Upvotes: 5

Avinash Verma
Avinash Verma

Reputation: 2762

Try this type header for Retrofit 1.9 and 2.0. For the JSON content type.

@Headers({"Accept: application/json"})
@POST("user/classes")
Call<playlist> addToPlaylist(@Body PlaylistParm parm);

You can add many more headers, i.e,

@Headers({
        "Accept: application/json",
        "User-Agent: Your-App-Name",
        "Cache-Control: max-age=640000"
    })

Dynamically add to headers:

@POST("user/classes")
Call<ResponseModel> addToPlaylist(@Header("Content-Type") String content_type, @Body RequestModel req);

Call your method, i.e.,

mAPI.addToPlayList("application/json", playListParam);

Or

Want to pass every time, then create an HttpClient object with the HTTP Interceptor:

OkHttpClient httpClient = new OkHttpClient();
        httpClient.networkInterceptors().add(new Interceptor() {
            @Override
            public com.squareup.okhttp.Response intercept(Chain chain) throws IOException {
                Request.Builder requestBuilder = chain.request().newBuilder();
                requestBuilder.header("Content-Type", "application/json");
                return chain.proceed(requestBuilder.build());
            }
        });

Then add to a Retrofit object

Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL).client(httpClient).build();

If you are using Kotlin, remove the { }. Else it will not work.

Upvotes: 130

jamian
jamian

Reputation: 1652

After trying a couple of times i figured out the answer.

The error

java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 2 path $

was coming due the failure of parsing the json.

In the method call I was passing a String instead of a POJO class.

@Headers("user-key: 9900a9720d31dfd5fdb4352700c")
@GET("api/v2.1/search")
Call<String> getRestaurantsBySearch(@Query("entity_id") String entity_id, @Query("entity_type") String entity_type, @Query("q") String query);

I should have passed instead of Call<String> the type of Call<Data>

Data being the Pojo class

something like this

@Headers("user-key: 9900a9720d31dfd5fdb4352700c")
@GET("api/v2.1/search")
Call<Data> getRestaurantsBySearch(@Query("entity_id") String entity_id, @Query("entity_type") String entity_type, @Query("q") String query);

Upvotes: 9

Raghunandan
Raghunandan

Reputation: 133560

You can use the below

 @Headers("user-key: 9900a9720d31dfd5fdb4352700c")
 @GET("api/v2.1/search")
 Call<String> getRestaurantsBySearch(@Query("entity_id") String entity_id, @Query("entity_type") String entity_type, @Query("q") String query);

and

 Call<String> call = endpoint.getRestaurantsBySearch("3","city","cafes");

The above is based in the zomato api which is documented at

https://developers.zomato.com/documentation#!/restaurant/search

Thing to note is the end point change api/v2.1/search and the Header @Headers("user-key: 9900a9720d31dfd5fdb4352700c").

Also check your base url .baseUrl("https://developers.zomato.com/")

Also i tried the above with a api key i generated and it works and my query was cafes as suggested the zomato documentation.

Note : I hope you have the below

 .addConverterFactory(ScalarsConverterFactory.create()) // for string conversion
 .build();

and the below in build.gradle file

compile group: 'com.squareup.retrofit2', name: 'converter-scalars', version: '2.2.0'

Edit:

You can also pass header with dynamic value as below

@GET("api/v2.1/search")
Call<String> getRestaurantsBySearch(@Query("entity_id") String entity_id, @Query("entity_type") String entity_type, @Query("q") String query,@Header("user-key") String userkey);

And

Call<String> call = endpoint.getRestaurantsBySearch("3","city","cafes","9900a9720d31dfd5fdb4352700c");

Upvotes: 54

Manoj Perumarath
Manoj Perumarath

Reputation: 10214

enter image description here

Please take a look at the response. It clearly shows that the api key you provided is wrong. At first you get the correct api key. Then call the request it will work .

Upvotes: 0

Nitin Joshi
Nitin Joshi

Reputation: 240

As far as i can see you are passing the data in a wrong way. Your method getRestaurantsBySearch is accepting the last two parameter as header field i.e accept and user-key. But while calling the method you are passing headers first. Pass the data as you have declared it in method signature of getRestaurantsBySearch

Upvotes: 0

Related Questions