Create a List in Android using RecyclerView — Part 2

Rohit Rawat
The Startup
Published in
11 min readMar 29, 2020

--

Welcome

In Part 1 of this series, we created a list in Android using some static movies data. But what if we want to populate a list using some web service like some news articles. Welcome to Part 2 of ‘Create a List in Android using RecyclerView’ series.

In this article we will be creating a list in Android using RecyclerView, Retrofit and a Web Service. We have already discussed about RecyclerView in Part 1.

According to Wikipedia, Web service is either a service offered by an electronic device to another electronic device or a server running on a computer device, listening for requests at a particular port over a network. In our case it is a server of NewsAPI. So basically we are going to use NewsAPI as our Web Service in this article.

Retrofit is an REST client library for Java and Android which is used to create HTTP request and process HTTP response from a REST API (Web Service). It was developed by Square Inc. In this article we are going to request and receive JSON data from NewsAPI but do note that Retrofit can also be used to request and receive other type of data like SimpleXML and Jackson.

Although, there are other HTTP Clients too, but Retrofit is the most popular among them. I have used Volley by Google and AsyncTask class from Android library to request APIs, but switched to Retrofit due to its seamless request and response experience. It focuses on the abstraction of HTTP request/response. Let’s jump right in.

Dependencies

Retrofit is not a part of Android legacy library, so we need to add a dependency for it too, just like we did for RecyclerView.

implementation 'com.squareup.retrofit2:retrofit:2.1.0'
implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
implementation 'androidx.recyclerview:recyclerview:1.1.0'

You may notice that here only one dependency belongs to retrofit. So, what are the others?

Retrofit GSON is a converter library which uses Google GSON for serialization of strings to and from JSON type objects. Google GSON is itself a serialization/de-serialization library but Retrofit has overriden it to make it work with its network requests.

OkHTTP Logging Interceptor (sounds heavy), is a HTTP request logger. By this, I mean when we face trouble with request or response in Retrofit, this library comes handy. It logs all the information about every request/response you make using Retrofit, to Android Studio Logcat.

After adding these dependencies, the dependency section of your app-level build.gradle should look like this:

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// AndroidX Support
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
// RecyclerView
implementation 'androidx.recyclerview:recyclerview:1.1.0'
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.1.0'
// Retrofit Gson Converter
implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
// HttpLoggingInterceptor
implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

After synchronizing, you are ready to rock!

We will try to work a little professionally here from now on. First of all, we will put our Java classes in contextual packages. We will be using Java interface for some tasks in this article. And we will be using some industry level code conventions in this and upcoming articles. Feel free to ask questions about anything that strikes odd to you. I ensure you that using these code conventions will improve your code quality and workflow. Try to catch-up.

Permission to sail the ship!

So, far we haven’t done anything that requires us to make changes in THE ANDROIDMANIFEST.XML. For now just know that whenever we have to access the resources of an Android device, we have to tell the OS that ‘we are going to need these resources in our application’. And how do we do that? By writing something in a file called AndroidManifest.xml. It defines the structure, metadata, components and requirements of our application.

So open AndroidManifest.xml and add internet permission line in it.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.prezrohit.recyclerviewexample">
<uses-permission android:name="android.permission.INTERNET" /> <application
android:allowBackup="true"
...
...

A brief look at the Web Service

As I said earlier that we will be using a Web Service in this article, so at this point you need to visit https://newsapi.org/ and set a free account.

Account Setup Successful Screen

After successfully setting up your account, go back to home.

Go to ‘Documentation’ in the title bar and there you will be able to see the different API endpoints you can hit using the API key provided. Try copying a link and pasting it in your browser (I’ll recommend Firefox Developer Edition for this). You’ll see something like this:

Top Headlines API Endpoint Response

So, basically what we do is populate this raw data in some fancy looking Android layout. And this is just sufficient data to show Top Headlines from NewsAPI.

Now, let’s set up our API Client which will be used here to load this data. Create a Java class named WebServiceClient.java and copy the following code in it:

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class WebServiceClient {

static Retrofit getClient() {

OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY)).build();

return new Retrofit.Builder()
.baseUrl("http://newsapi.org/v2/")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
}

}

Here, we have created a static method getClient(), which returns a Retrofit object by creating it using base URL, GsonConverterFactory, OkHTTPClient and OkHTTPLoggingInterceptor.

Now to capture response data, we need to create some DTOs (Data Transfer Objects which are just Java classes used to carry data from and to client and server). If you take a look at the Top Headlines response, you will notice that it contains three fields at the root level namely — status (a String), totalResults (an Integer but we can declare it as String since we are using GsonConverterFactory and it will do the conversion for us implicitly), articles (an Array of Article type). Further we see that Source field inside Article class is having some fields inside it too. So, we conclude that here we are going to use bottom-up approach. Let’s code it down.

Source.java

import com.google.gson.annotations.SerializedName;

public class Source {

@SerializedName("id")
private String id;

@SerializedName("name")
private String name;

public String getId() {
return id;
}

public String getName() {
return name;
}
}

Article.java

import com.google.gson.annotations.SerializedName;

public class Article {

@SerializedName("source")
private Source source;

@SerializedName("author")
private String author;

@SerializedName("title")
private String title;

@SerializedName("description")
private String description;

@SerializedName("url")
private String url;

@SerializedName("urlToImage")
private String urlToImage;

@SerializedName("publishedAt")
private String publishedAt;

@SerializedName("content")
private String content;

public Source getSource() {
return source;
}

public String getAuthor() {
return author;
}

public String getTitle() {
return title;
}

public String getDescription() {
return description;
}

public String getUrl() {
return url;
}

public String getUrlToImage() {
return urlToImage;
}

public String getPublishedAt() {
return publishedAt;
}

public String getContent() {
return content;
}

}

TopHeadlines.java

import com.google.gson.annotations.SerializedName;

import java.util.List;

public class TopHeadline {

@SerializedName("status")
private String status;

@SerializedName("totalResults")
private String totalResults;

@SerializedName("articles")
private List<Article> articleList;

public String getStatus() {
return status;
}

public String getTotalResults() {
return totalResults;
}

public List<Article> getArticleList() {
return articleList;
}

}

Two things to notice here, first being @SerializedName() — it is used to pass a name to the converter factory to help in the serialization and de-serialization of JSON data into Java objects Meaning that after writing @SerializedName("status") the value of status key will be stored in the status field of TopHeadlines class object.

Second thing to notice is that we don’t need to declare setter methods in our response DTOs since we are never going to set their values — they are receiver objects after all.

To simplify this DTO creation process, you can use some online tools to get the class definitions of required DTOs by passing your response there. One such tool is jsonschema2pojo. Just copy the API response and paste into the placeholder at this website and click on preview. Done!

Now we are ready to define a web service interface named WebService.java. It contains API endpoint calling methods and determine what type of data we need to read from a certain endpoint.

WebService.java

import com.cradle.recyclerviewexample.dto.TopHeadline;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

interface WebService {

@GET("top-headlines")
Call<TopHeadline> getTopHeadlines(@Query("country") String country, @Query("apiKey") String apiKey);

}

‘top-headlines’ is a GET endpoint — hence the @GET annotation and it takes two query parameters namely — country and apiKey. So, when we will call this endpoint we need to pass these two parameters or the API won’t hit or throw some error. This is the power of Java interface.

We need to create a row item layout for the list, just like we did in the previous article in this series. A row layout file defines how the items are going to look in our list.

For that we are gonna another AndroidX Support Library called CardView which just makes the view look like a card like who might have seen in some Google Apps.

Now open your app level build.gradle and add the following line to the dependency section and sync gradle.

implementation "androidx.cardview:cardview:1.0.0"

Now create a layout file and name it row_news.xml and copy the following code into it.

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:elevation="8dp"
android:padding="10dp"
app:cardBackgroundColor="#E5E5E5"
app:cardCornerRadius="12dp">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">

<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:elevation="0dp"
app:cardCornerRadius="12dp"
android:layout_gravity="center">

<ImageView
android:id="@+id/img_thumbnail"
android:layout_width="80dp"
android:layout_height="80dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_thumbnail" />

</androidx.cardview.widget.CardView>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">

<TextView
android:id="@+id/txt_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginEnd="10dp"
android:ellipsize="end"
android:maxLines="2"
android:padding="5dp"
android:text="This is a sample news title which has no intent"
android:textSize="18sp"
android:textStyle="bold" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:id="@+id/txt_source_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="2"
android:padding="5dp"
android:text="Washington Post"
android:maxLines="1"
android:ellipsize="end"
android:textSize="15sp" />

<TextView
android:id="@+id/txt_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:layout_weight="2"
android:padding="5dp"
android:text="9 Mar 2020"
android:maxLines="1"
android:ellipsize="end"
android:textAlignment="viewEnd"
android:textSize="15sp" />

</LinearLayout>

</LinearLayout>

</LinearLayout>

</androidx.cardview.widget.CardView>

What comes after row layout?

VIEWHOLDER. It’s a Java class that helps us to interact with the row layout inside the Adapter, ring any bell? Nevermind, you’ll get the hang of it after a while.

Please note that in this case, we are going to need to load a thumbnail for our news story which is basically an image. It is recommended to use a third party library like Picasso or Glide to load image from a URL in Android. So we are going to use Glide here. Open build.gradle, again, and paste the following line into the dependency section and then sync gradle.

implementation 'com.github.bumptech.glide:glide:4.11.0'

Now, create a new java class and name it NewsViewHolder.java and write the following code in it.

import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.cradle.recyclerviewexample.R;

public class NewsViewHolder extends RecyclerView.ViewHolder {

private TextView txtTitle;
private TextView txtSourceName;
private TextView txtDate;
private ImageView imgThumbnail;

public NewsViewHolder(@NonNull View itemView) {
super(itemView);

txtTitle = itemView.findViewById(R.id.txt_title);
txtSourceName = itemView.findViewById(R.id.txt_source_name);
txtDate = itemView.findViewById(R.id.txt_date);
imgThumbnail = itemView.findViewById(R.id.img_thumbnail);
}

public void setTitle(String title) {
this.txtTitle.setText(title);
}

public void setSourceName(String sourceName) {
this.txtSourceName.setText(sourceName);
}

public void setDate(String date) {
this.txtDate.setText(date);
}

public void setThumbnail(Context context, String url) {
Glide.with(context)
.load(url)
.centerCrop()
.placeholder(R.drawable.ic_thumbnail)
.apply(new RequestOptions().override(100, 100))
.into(this.imgThumbnail);
}

}

You might notice the way image is being loaded here by Glide. It first takes a context instance, a URL of course, then we chose to center crop the image into our view, then there’s a placeholder until the image isn’t loaded, then we tell Glide to fit the image into the resolution of 100*100 before loading it into our ImageView and finally we load the image into our thumbnail image view.

But, who controls the ViewHolder?

Remember something called Adapter? Yeah, cool. That’s where we are. Now create a Adapter class named NewsAdapter.java and copy the following code into it.

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.cradle.recyclerviewexample.R;
import com.cradle.recyclerviewexample.dto.Article;
import com.cradle.recyclerviewexample.dto.TopHeadline;
import com.cradle.recyclerviewexample.ui.NewsViewHolder;

import java.util.List;

public class NewsAdapter extends RecyclerView.Adapter<NewsViewHolder> {

private Context context;
private List<Article> articleList;

public NewsAdapter(Context context, List<Article> articleList) {
this.context = context;
this.articleList = articleList;
}

@NonNull
@Override
public NewsViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.row_news, parent, false);
return new NewsViewHolder(view);
}

@Override
public void onBindViewHolder(@NonNull NewsViewHolder holder, int position) {
Article article = articleList.get(position);
holder.setSourceName(article.getSource().getName());
holder.setTitle(article.getTitle());
holder.setDate(article.getPublishedAt());
holder.setThumbnail(context, article.getUrlToImage());
}

@Override
public int getItemCount() {
return articleList == null ? 0 : articleList.size();
}

}

We are almost done and are at the controller level right now. Meaning, we can now move to step where we will be populating the list. But before that we need to have a list. Don’t we? Navigate to activity_main.xml and create a RecyclerViewand a ProgressBarelement there.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.MainActivity">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_movies"
android:layout_height="match_parent"
android:layout_width="match_parent"
tools:listitem="@layout/row_news"
/>

<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerInParent="true"
/>

</RelativeLayout>

We created a ProgressBar element in our view because it makes the UX better to show a progress bar when we are loading data from a remote source and we want the user to wait.

Now, where do we get our data from? Remember, we talked something about HTTP request. But, how do we do that? Navigate to MainActivity.java.

First of all we need to set up views and list objects in our Activity class.

public class MainActivity extends AppCompatActivity {

private static final String TAG = "MainActivity";

private RecyclerView rvNews;
private ProgressBar progressBar;

private List<Article> articleList;

private NewsAdapter newsAdapter;
...
...
...
}
private void init() {
articleList = new ArrayList<>();
rvNews = findViewById(R.id.rv_movies);
progressBar = findViewById(R.id.progress_bar);
}
private void setRecyclerView() {
rvNews.setLayoutManager(new LinearLayoutManager(this));
rvNews.setAdapter(newsAdapter);
}

setRecyclerView() method sets the adapter and layout manager to the recycler view. We will use this method once the data is loaded from the web service. Now over to the final step.

private void getNews() {
WebService webService = WebServiceClient.getClient().create(WebService.class);
Call<TopHeadline> call = webService.getTopHeadlines("in", Helper.API_KEY);
call.enqueue(new Callback<TopHeadline>() {
@Override
public void onResponse(Call<TopHeadline> call, Response<TopHeadline> response) {
progressBar.setVisibility(View.GONE);
Log.d(TAG, "onResponse: " + response.code());
assert response.body() != null : "Response Body Empty";
articleList = response.body().getArticleList();
newsAdapter = new NewsAdapter(MainActivity.this, articleList);
setRecyclerView();
}

@Override
public void onFailure(Call<TopHeadline> call, Throwable t) {
progressBar.setVisibility(View.GONE);
Log.d(TAG, "onFailure: " + t.getLocalizedMessage());
}
});
}

We created a static getClient() method in WebServiceClient class. Here, we use that function to get a Retrofit object which is further used to create an instance of WebService class — which basically performs the API requests. Then we call the target method getTopHeadlines() passing the two required paramaters and then finally enqueue the task to be performed by Retrofit.

This creates two ways for the flow of the program — onResponse() and onFailure(). As the name suggests, when the request is successful and we get a response from the server, the flow goes to onResponse() callback method otherwise in case of an error in getting a response, onFailure() is called. It is totally up to us what task we want to perform inside these callback method. We can setup big list here or change the view to an error message in case of an error or just print a simple message in the Logcat. But for this case we just used the Article and TopHeadlines classes’ getter methods to populate a list for our beloved recycler view. Then finally setup the recycler view by calling the setRecyclerView() method.

In case of an error we simply the error message to the Logcat and we’re done. But remember, at a professional level there’s a lot more to it. You cannot just leave it like this. You have got to notify the user what error occured in some user friendly way — for instance, change the UI or show a dialogue.

So all of it being said and done. We are at the happy position to wrap this up. I hope this article helps you in ways. Stay connected to the upcoming article in this series. Till then, keep writing good code!

--

--

Rohit Rawat
The Startup

A frontend developer proficient in Android, React.js and with a 2 years of industry experience in Android.