Last Updated on April 24, 2018 by Aram
We have come to the 3rd and last part of the Retrofit tutorial in Android, which is passing HTTP request headers using Retrofit.
If you are new to Retrofit, I recommend that you read the previous 2 tutorials in the series, you can find the links below, otherwise just continue exploring this tutorial.
Retrofit Tutorial in Android – Part 1 Introduction
Retrofit Tutorial in Android – Part 2 POST Requests
In HTTP-based Web Services, we sometimes have to pass extra details when doing API requests. Such details will neither be passed through the URL itself or query strings, nor via the request body. In this case, we will have to pass them via the so called Request Headers.
Headers can be related to defining response type, request type, authorization, language and many others. Please refer to Wikipedia’s page for a full list of HTTP Headers.
In this tutorial, we will learn 2 ways to pass the HTTP request headers, either via Retrofit’s annotations or via an intercepter. It is important to notice that the interceptor methodology is not something specific to Retrofit, but it comes from the OkHttp library, which is the core library underneath Retrofit.
To better explain how can we pass headers using Retrofit, I thought that the best example would be doing an Authenticated API Call to Oxford Dictionaries.
First thing that we need to do is to obtain API Key and password from the Developer section of the Oxford Dictionaries website.
Open the website, click on Get Your API Key , choose the plan you want and then follow the registration process. Once you are logged in, you can go into API Credentials sections and generate your API Key. You can notice the base URL to do the HTTP Request and the Application ID. Do not use my ID and Key, just go to the Oxford Dictionaries website, sign up and obtain your free API key. It is super easy and straightforward.
With each HTTP Request from your app to Oxford Dictionaries API, you should pass the correct Application ID and the API Key. Failure to provide any of which will result in an HTTP 403 Authentication Failed Message.
Now that we have the Application ID and the API Key, let’s go back to Android Studio, and start writing our app that will do authenticated requests to Oxford Dictionaries API, and display meanings for words entered by the user.
We will go through the creation steps quickly, as these have been explained in details in the first tutorial of the Retrofit Tutorial in Android series.
From Android Studio, start with a blank project. Open Gradle file and reference the retrofit and retrofit converter-gson libraries.
1 2 |
implementation 'com.squareup.retrofit2:retrofit:2.3.0' implementation 'com.squareup.retrofit2:converter-gson:2.3.0' |
Sync the project to install the retrofit libraries and dependencies. And don’t forget to include the INTERNET Permission into your manifest file.
1 |
<uses-permission android:name="android.permission.INTERNET"/> |
Before we create Retrofit Interface, we need to prepare the data models to receive the response from the Oxford Dictionaries Entries API.
If you try calling the the dictionary entries endpoint using your app_id and app_key, using any of your preferred tools to do HTTP API Calls such as Oxford Dictionaries API Documentation itself or Postman App or hurl.it, you should get the following JSON response, assuming that the URL is: https://od-api.oxforddictionaries.com/api/v1/entries/en/oxford
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
{ "metadata": { "provider": "Oxford University Press" }, "results": [ { "id": "oxford", "language": "en", "lexicalEntries": [ { "entries": [ { "grammaticalFeatures": [ { "text": "Proper", "type": "Properness" }, { "text": "Singular", "type": "Number" } ], "homographNumber": "100", "senses": [ { "definitions": [ "a city in central England, on the River Thames, the county town of Oxfordshire; population 146,100 (est. 2009). Oxford University is located there." ], "domains": [ "University" ], "id": "m_en_gbus0738190.005", "short_definitions": [ "city in central England, on River Thames" ] } ] }, { "etymologies": [ "mid 19th century: from Oxford" ], "grammaticalFeatures": [ { "text": "Singular", "type": "Number" } ], "homographNumber": "200", "senses": [ { "definitions": [ "a thick cotton fabric chiefly used to make shirts" ], "domains": [ "Textiles" ], "examples": [ { "text": "an Oxford shirt" } ], "id": "m_en_gbus0738200.008", "notes": [ { "text": "mass noun", "type": "grammaticalNote" } ], "short_definitions": [ "thick cotton fabric used to make shirts" ], "variantForms": [ { "text": "Oxford cloth" } ] }, { "definitions": [ "a type of lace-up shoe with a low heel" ], "domains": [ "Shoemaking" ], "examples": [ { "text": "suede Oxfords are the essential shoes for autumn" } ], "id": "m_en_gbus0738200.013", "short_definitions": [ "type of lace-up shoe with low heel" ], "variantForms": [ { "text": "Oxford shoe" } ] } ], "variantForms": [ { "text": "oxford" } ] } ], "language": "en", "lexicalCategory": "Noun", "pronunciations": [ { "audioFile": "http://audio.oxforddictionaries.com/en/mp3/oxford_gb_2.mp3", "dialects": [ "British English" ], "phoneticNotation": "IPA", "phoneticSpelling": "ˈɒksfəd" } ], "text": "Oxford" } ], "type": "headword", "word": "Oxford" } ] } |
For this we need to create a set of java classes (POJO) , which stands for Plain Old Java Object. These should match the Json response in terms of structure and field names. We don’t have to create every single structure and field, we only create the needed ones, that we will be using in our app.
To organize the project’s structure, create a models package folder under your main -> java -> {app package folder} . We will create all the Dictionary Related Classes under this folder.
First we will create the DictionaryInfo Class
1 2 3 4 5 6 7 8 9 10 11 12 |
package codingsonata.com.oxforddictionariesapp.models; import java.util.List; public class DictionaryInfo { private List<DictionaryResult> results; public List<DictionaryResult> getResults() { return results; } } |
Then the DictionaryResult Class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package codingsonata.com.oxforddictionariesapp.models; import java.util.List; public class DictionaryResult { private List<LexicalEntry> lexicalEntries; private String word; public List<LexicalEntry> getLexicalEntries() { return lexicalEntries; } public String getWord() { return word; } } |
After that, the LexicalEntry Class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package codingsonata.com.oxforddictionariesapp.models; import java.util.List; public class LexicalEntry { private List<Entry> entries; private String lexicalCategory; public List<Entry> getEntries() { return entries; } public String getLexicalCategory() { return lexicalCategory; } } |
Next create the Entry Class:
1 2 3 4 5 6 7 8 9 10 11 12 |
package codingsonata.com.oxforddictionariesapp.models; import java.util.List; public class Entry { private List<Sense> senses; public List<Sense> getSenses() { return senses; } } |
Then create the Sense Class
1 2 3 4 5 6 7 8 9 10 11 12 |
package codingsonata.com.oxforddictionariesapp.models; import java.util.List; public class Sense { private List<String> definitions; public List<String> getDefinitions() { return definitions; } } |
Now that we have the full object graph created, let’s create the Retrofit Api Interface. We will name it IDictionariesApi, and define one endpoint inside it to get the dictionary entries of a given word.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package codingsonata.com.oxforddictionariesapp; import codingsonata.com.oxforddictionariesapp.models.DictionaryInfo; import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Header; import retrofit2.http.Path; public interface IDictionariesApi { @GET("entries/en/{word}") Call<DictionaryInfo> getDictionaryEntries(@Header("app_id") String id, @Header("app_key") String key, @Path("word") String word); } |
Notice above, we have a new annotation @Header, this is a Retrofit specific annotation which will allow you the pass the request headers to the targeting HTTP endpoint, where every argument represents a request header entry.
Next, let’s create our ApiManager Class which will be used to hold the singleton instance of our Retrofit service client with the base URL pointing to the Oxford Dictionaries API, https://od-api.oxforddictionaries.com/api/v1/ , and it will contain the method call to get the dictionary entries.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
import codingsonata.com.oxforddictionariesapp.models.DictionaryInfo; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class ApiManager { private static final String APP_ID = "e1477e44"; private static final String APP_KEY = "84b1ef7d35856376bf460d274b81e60d"; private static ApiManager apiManager; private final IDictionariesApi service; private ApiManager() { Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://od-api.oxforddictionaries.com/api/v1/") .addConverterFactory(GsonConverterFactory.create()) .build(); service = retrofit.create(IDictionariesApi.class); } public static ApiManager getInstance() { if (apiManager == null) { apiManager = new ApiManager(); } return apiManager; } public void getDictionaryEntries(String word, Callback<DictionaryInfo> callback) { Call<DictionaryInfo> dictionaryEntries = service.getDictionaryEntries(APP_ID, APP_KEY, word); dictionaryEntries.enqueue(callback); } } |
Don’t forget to extend the Application Class so that you can instantiate the singleton instance of the ApiManager upon the start of the application
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package codingsonata.com.oxforddictionariesapp; import android.app.Application; public class MainApplication extends Application { public static ApiManager apiManager; @Override public void onCreate() { super.onCreate(); apiManager = ApiManager.getInstance(); } } |
And add the name attribute in the Application section of AndroidManifest.xml file to become the .MainApplication
1 2 |
<application android:name=".MainApplication" |
If you don’t wish to use the @Header annotation of the Retrofit library to pass the needed request headers, you can use the interceptor methodology. It is very simple to implement the intercepter, you only need to create an OkHttp instance and add a new Interceptor class in it, and then inside the intercept method, you will inject the headers you want within the request chain and then proceed the request.
In the ApiManager constructor, do the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
OkHttpClient client = new OkHttpClient().newBuilder().addInterceptor(new Interceptor() { @Override public Response intercept(@NonNull Chain chain) throws IOException { Request request = chain.request() .newBuilder() .addHeader("app_id", APP_ID) .addHeader("app_key", APP_KEY) .build(); return chain.proceed(request); } }).build(); Retrofit retrofit = new Retrofit.Builder() .client(client) .baseUrl("https://od-api.oxforddictionaries.com/api/v1/") .addConverterFactory(GsonConverterFactory.create()) .build(); service = retrofit.create(IDictionariesApi.class); |
And then remove the extra paramters we added in the getDictionaryEntries method in ApiManager and in the Retrofit Interface.
Using the interceptor methodology is a better approach to inject request headers into your HTTP requests, this will be really useful when your app grows with more endpoints to the same APIs, where you don’t have to pass the request headers each time, the request headers will already be injected upon the initialization of the app.
Now we will jump up to the UI layer of the app, and we will start preparing the layout of our dictionary app.
In the activity_main.xml file, we want to have few widgets: an EditText for the user to input the search word, a Button to do the API call and a ListView to display the results. We will also add a progress bar spinner to show the user while the API call is taking place.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="10dp" tools:context=".MainActivity"> <LinearLayout android:id="@+id/search_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <EditText android:id="@+id/search_edit_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="2.6" android:hint="Type word to search..." android:inputType="text" /> <Button android:id="@+id/search_button" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="@android:drawable/ic_search_category_default" /> </LinearLayout> <ProgressBar android:id="@+id/progress_bar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="gone" app:layout_constraintTop_toBottomOf="@+id/search_layout" /> <TextView android:id="@+id/word" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:text="Word..." android:textAppearance="@style/TextAppearance.AppCompat.Large" android:textColor="@color/colorPrimaryDark" android:textStyle="bold" android:visibility="gone" app:layout_constraintTop_toBottomOf="@+id/search_layout" /> <ListView android:id="@+id/dictionary_entries" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" app:layout_constraintTop_toBottomOf="@+id/word" /> </android.support.constraint.ConstraintLayout> |
And to populate the ListView, we want to have a custom layout that will display the lexical category of the entry such as noun, verb …etc. , and the definition of the word. We will style it in a way that will be appealing to user. As you already know, there are numerous words in English that can have more than 1 definition per its lexical category.
So, let’s create a new xml layout file and name it dictionary_entry.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" android:orientation="vertical"> <TextView android:id="@+id/lexical_category" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:text="Noun or Verb or ..." android:textAppearance="@style/Base.TextAppearance.AppCompat.Large" /> <TextView android:id="@+id/definition" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:text="Definition" android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium" /> </LinearLayout> |
Now we have both our layouts ready to be inflated from the code.
Let’s first go and create the adapter. It will extend the BaseAdapter Class, and it will follow the ViewHolder pattern to effectively inflate/bind the ListView.
ViewHolder pattern is an effective way to display items in list, this is particularly effective when the list is long to have a scroll. This means that the operation findViewById will not be called every time the layout is inflated (in the case of scrolling) , but a ViewHolder Class will store direct reference to the UI Elements and then this ViewHolder will be stored in a tag of the view after inflating it. So the next render of the List Item’s row will get the references from the tag and populate the needed values, it will not call the findViewById again.
If you use a RecyclerView, it is a must that you follow the ViewHolder pattern, as it is built on it. I could simply used the RecyclerView in this tutorial, as previous tutorials, but I wanted to show how we can use the ListView and how can we apply the ViewHolder pattern in it.
If you want to read more about ViewHolder pattern, check this link.
Now back to our app, create a class with name DictionaryAdapter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
package codingsonata.com.oxforddictionariesapp; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; import java.util.List; import codingsonata.com.oxforddictionariesapp.models.Entry; import codingsonata.com.oxforddictionariesapp.models.LexicalEntry; import codingsonata.com.oxforddictionariesapp.models.Sense; public class DictionaryAdapter extends BaseAdapter { private List<LexicalEntry> lexicalEntries; private Context context; public DictionaryAdapter(Context context, List<LexicalEntry> lexicalEntries) { this.context = context; this.lexicalEntries = lexicalEntries; } public void setLexicalEntries(List<LexicalEntry> lexicalEntries) { this.lexicalEntries = lexicalEntries; notifyDataSetChanged(); } @Override public int getCount() { return lexicalEntries.size(); } @Override public Object getItem(int i) { return lexicalEntries.get(i); } @Override public long getItemId(int i) { return i; } @Override public View getView(int i, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null) { convertView = LayoutInflater.from(context).inflate( R.layout.dictionary_entry, parent, false); viewHolder = new ViewHolder(convertView); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } LexicalEntry lexicalEntry = (LexicalEntry) getItem(i); Entry entry = lexicalEntry.getEntries().get(0); Sense sense = entry.getSenses().get(0); viewHolder.definition.setText(sense.getDefinitions().get(0)); viewHolder.lexicalCategory.setText(lexicalEntry.getLexicalCategory()); return convertView; } class ViewHolder { private TextView lexicalCategory; private TextView definition; public ViewHolder(View view) { this.lexicalCategory = view.findViewById(R.id.lexical_category); this.definition = view.findViewById(R.id.definition); } } } |
Finally, let’s go to our MainActivity class, and glue all the above.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
package codingsonata.com.oxforddictionariesapp; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.EditText; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import java.io.IOException; import java.util.Collections; import codingsonata.com.oxforddictionariesapp.models.DictionaryInfo; import codingsonata.com.oxforddictionariesapp.models.DictionaryResult; import codingsonata.com.oxforddictionariesapp.models.LexicalEntry; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; public class MainActivity extends AppCompatActivity implements View.OnClickListener, Callback<DictionaryInfo> { private DictionaryAdapter adapter; private EditText searchEditText; private String word; private View progressBar; private TextView wordTextView; private ListView dictionaryEntriesListView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); dictionaryEntriesListView = findViewById(R.id.dictionary_entries); adapter = new DictionaryAdapter(this, Collections.<LexicalEntry>emptyList()); dictionaryEntriesListView.setAdapter(adapter); findViewById(R.id.search_button).setOnClickListener(this); searchEditText = findViewById(R.id.search_edit_text); progressBar = findViewById(R.id.progress_bar); wordTextView = findViewById(R.id.word); } @Override public void onClick(View view) { switch (view.getId()) { case R.id.search_button: word = searchEditText.getText().toString().toLowerCase(); progressBar.setVisibility(View.VISIBLE); MainApplication.apiManager.getDictionaryEntries(word, this); break; } } @Override public void onResponse(@NonNull Call<DictionaryInfo> call, @NonNull Response<DictionaryInfo> response) { progressBar.setVisibility(View.GONE); if (response.isSuccessful()) { DictionaryInfo body = response.body(); DictionaryResult dictionaryResult = body.getResults().get(0); wordTextView.setText(dictionaryResult.getWord()); adapter.setLexicalEntries(dictionaryResult.getLexicalEntries()); wordTextView.setVisibility(View.VISIBLE); dictionaryEntriesListView.setVisibility(View.VISIBLE); } else { dictionaryEntriesListView.setVisibility(View.GONE); wordTextView.setVisibility(View.GONE); switch (response.code()) { case 403: try { Toast.makeText(MainActivity.this, response.errorBody().string(), Toast.LENGTH_LONG).show(); } catch (IOException e) { e.printStackTrace(); } break; case 400: case 404: Toast.makeText(MainActivity.this, String.format("Invalid word %s", word), Toast.LENGTH_LONG).show(); break; } } } @Override public void onFailure(@NonNull Call<DictionaryInfo> call, @NonNull Throwable t) { progressBar.setVisibility(View.GONE); Toast.makeText(MainActivity.this, t.getMessage(), Toast.LENGTH_LONG).show(); } } |
In short, the above code will create the DictionaryAdapter with initial values, and it will prepare the needed UI elements. Clicking the search button, will trigger a call to the Dictionary Entries Endpoint of the Oxford Dictionaries API , using the app_id and app_key that we defined in the ApiManager class. and then if the call happens and returns 200, we will populate the list view with the response after mapping it (automatically) with the DictionaryInfo object.
If the response contained multiple lexical categories, then these will displayed on top of each other , displaying a scrollbar if the output extends the viewport of the screen.
Now, let’s run our app and see the results.
Conclusion
This was the 3rd and last tutorial in our series of implementing Retrofit in Android. In this tutorial we covered a very important and commonly used functionality which is passing request headers to API calls, where we discussed the 2 ways to add the request headers: Retrofit Annotations and the OkHttp Interceptor Class. We discussed and wrote an app that will connect to the popular Oxford Dictionaries API.
Please let me know if you liked this tutorial and the previous 2 tutorials. and don’t forget to share these tutorials on your social network to help spread the word for all learners.
Bonus
Allow me to share with you this wonderful piano sonata. This is one of my favourite masterpieces composed by the music Virtuoso, W. A. Mozart.
It is piano sonata No 14 in C minor (K457), includes 3 movements.