Covid 19 Get All Country Data using Volley library

COVID-19 All Countries Data in Android with Volley & RecyclerView (2026) | D.Tech Academic

COVID-19 All Countries Data in Android with Volley & RecyclerView (2026)

Last Updated: | Author: Deep Singh

In the previous tutorial, we fetched COVID-19 data for a single country. Now let's go bigger — fetch the data for every country in the world and display it as a scrollable grid of cards, each showing the country name and its total case count.

You'll learn how to parse a JSON array into a List<Case> with Gson, display it in a RecyclerView with GridLayoutManager, and open a dialog with more details when a card is tapped. This is the exact pattern you'll use for any list-driven Android screen — a list of products, users, articles, or anything else.

Heads-up on the API: The original 2021 tutorial used coronavirus-19-api.herokuapp.com, which was shut down when Heroku discontinued free dynos in November 2022. The code has been updated to use disease.sh, an active replacement that serves the same data.

Output Screen

COVID-19 tracker app in Android showing all countries in a RecyclerView grid
All countries displayed in a two-column grid

Gradle Dependencies

Add these to your app-level build.gradle:

dependencies {
    implementation 'com.android.volley:volley:1.2.1'
    implementation 'com.google.code.gson:gson:2.11.0'
    implementation 'androidx.recyclerview:recyclerview:1.3.2'
    implementation 'androidx.cardview:cardview:1.0.0'
}

What changed from the original:

  • Volley upgraded from 1.1.0 to 1.2.1 (bug fixes and security).
  • Gson upgraded from 2.8.5 to 2.11.0 (the older version has known vulnerabilities).
  • RecyclerView upgraded from 1.1.0 to 1.3.2.

AndroidManifest.xml

The app needs internet access to fetch the API data.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.livejsonparsing">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

</manifest>

Fix: The original used <uses-permission-sdk-23> for INTERNET. That element is meant for runtime permissions on API 23+ (like camera or location), not for install-time permissions like internet access. It should be a plain <uses-permission>.

Layouts

activity_main.xml

<?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="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <Button
        android:id="@+id/fetchButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Load All Countries"
        android:onClick="getData" />

    <ProgressBar
        android:id="@+id/progress"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:visibility="gone" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/myrecyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="12dp" />

</LinearLayout>

case_item.xml — Each Card in the Grid

<?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="6dp"
    app:cardCornerRadius="8dp"
    app:cardElevation="4dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="16dp">

        <TextView
            android:id="@+id/countryname"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:textStyle="bold"
            android:maxLines="1"
            android:ellipsize="end" />

        <TextView
            android:id="@+id/countrycase"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingTop="6dp"
            android:textSize="14sp" />

    </LinearLayout>

</androidx.cardview.widget.CardView>

What's new: The original post never showed the layouts at all — the tutorial jumped straight into Java. This version includes both the activity layout and the item layout, so you can build the app without guessing.

Case.java

This is the data model. Gson maps each JSON object in the response array to an instance of this class.

package com.example.livejsonparsing;

public class Case {

    private String country;
    private int cases;
    private int todayCases;
    private int deaths;
    private int todayDeaths;
    private int recovered;
    private int active;
    private int critical;

    public String getCountry() { return country; }
    public void setCountry(String country) { this.country = country; }

    public int getCases() { return cases; }
    public void setCases(int cases) { this.cases = cases; }

    public int getTodayCases() { return todayCases; }
    public void setTodayCases(int todayCases) { this.todayCases = todayCases; }

    public int getDeaths() { return deaths; }
    public void setDeaths(int deaths) { this.deaths = deaths; }

    public int getTodayDeaths() { return todayDeaths; }
    public void setTodayDeaths(int todayDeaths) { this.todayDeaths = todayDeaths; }

    public int getRecovered() { return recovered; }
    public void setRecovered(int recovered) { this.recovered = recovered; }

    public int getActive() { return active; }
    public void setActive(int active) { this.active = active; }

    public int getCritical() { return critical; }
    public void setCritical(int critical) { this.critical = critical; }
}

CustomCaseAdapter.java

This is the RecyclerView adapter. It binds each Case object to a card in the grid and forwards tap events through a listener interface.

package com.example.livejsonparsing;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

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

import java.util.List;

public class CustomCaseAdapter
        extends RecyclerView.Adapter<CustomCaseAdapter.MyViewHolder> {

    private final List<Case> caseList;
    private CaseListener caseListener;

    public CustomCaseAdapter(List<Case> caseList) {
        this.caseList = caseList;
    }

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

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        Case ca = caseList.get(position);
        holder.tcountry.setText(ca.getCountry());
        holder.tcase.setText("Cases: " + ca.getCases());
    }

    @Override
    public int getItemCount() {
        return caseList.size();
    }

    public void setCaseListener(CaseListener listener) {
        this.caseListener = listener;
    }

    public interface CaseListener {
        void caseItem(View view, int position);
    }

    class MyViewHolder extends RecyclerView.ViewHolder
            implements View.OnClickListener {

        TextView tcountry, tcase;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            tcountry = itemView.findViewById(R.id.countryname);
            tcase = itemView.findViewById(R.id.countrycase);
            itemView.setOnClickListener(this);
        }

        @Override
        public void onClick(View view) {
            if (caseListener != null) {
                int position = getBindingAdapterPosition();
                if (position != RecyclerView.NO_POSITION) {
                    caseListener.caseItem(view, position);
                }
            }
        }
    }
}

What's been fixed from the original:

  • Removed the stray Log.e call. The original called Log.e("error", caseList.size() + "") inside onBindViewHolder — that fires on every scroll and floods the logcat. Removed.
  • Removed android.util.Log import. No longer needed.
  • Added null check for caseListener. If the listener is never set, tapping a card would crash with a NullPointerException. Now it safely does nothing.
  • Used getBindingAdapterPosition(). The original used the deprecated getAdapterPosition(). The new method handles the case where the item has been removed and returns NO_POSITION.
  • Better display text. Shows "Cases: 12345" instead of just the raw number, which is much more readable.
  • Made caseList final. It's assigned once in the constructor and never replaced.
  • Fixed the `onCreateViewHolder` continuation — the original had a stray line break that made it look like the code was split across two lines when it wasn't.

MainActivity.java

package com.example.livejsonparsing;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import org.json.JSONArray;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private ProgressBar progressBar;
    private RecyclerView recyclerView;
    private CustomCaseAdapter customCaseAdapter;
    private RequestQueue requestQueue;
    private final List<Case> caseList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        progressBar = findViewById(R.id.progress);
        recyclerView = findViewById(R.id.myrecyclerview);
        recyclerView.setLayoutManager(new GridLayoutManager(this, 2));

        customCaseAdapter = new CustomCaseAdapter(caseList);
        recyclerView.setAdapter(customCaseAdapter);

        requestQueue = Volley.newRequestQueue(this);

        customCaseAdapter.setCaseListener(new CustomCaseAdapter.CaseListener() {
            @Override
            public void caseItem(View view, int position) {
                Case ca = caseList.get(position);
                new AlertDialog.Builder(MainActivity.this)
                        .setTitle(ca.getCountry())
                        .setMessage(
                                "Total Cases : " + ca.getCases() + "\n" +
                                "Today Cases : " + ca.getTodayCases() + "\n" +
                                "Total Deaths : " + ca.getDeaths() + "\n" +
                                "Today Deaths : " + ca.getTodayDeaths() + "\n" +
                                "Recovered : " + ca.getRecovered() + "\n" +
                                "Active : " + ca.getActive() + "\n" +
                                "Critical : " + ca.getCritical()
                        )
                        .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        })
                        .show();
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (requestQueue != null) {
            requestQueue.cancelAll(this);
        }
    }

    public void getData(View view) {
        progressBar.setVisibility(View.VISIBLE);
        caseList.clear();
        customCaseAdapter.notifyDataSetChanged();

        String url = "https://disease.sh/v3/covid-19/countries";

        StringRequest sr = new StringRequest(
                Request.Method.GET,
                url,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        progressBar.setVisibility(View.GONE);
                        try {
                            JSONArray jsonArray = new JSONArray(response);
                            Gson gson = new Gson();

                            for (int i = 0; i < jsonArray.length(); i++) {
                                Case cs = gson.fromJson(
                                        jsonArray.getJSONObject(i).toString(),
                                        Case.class
                                );
                                if (cs != null) {
                                    caseList.add(cs);
                                }
                            }
                            customCaseAdapter.notifyDataSetChanged();
                        } catch (Exception e) {
                            Log.e("Parse", "Failed to parse response", e);
                            Toast.makeText(MainActivity.this,
                                    "Failed to parse data",
                                    Toast.LENGTH_SHORT).show();
                        }
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        progressBar.setVisibility(View.GONE);
                        Log.e("Volley", "Request failed", error);
                        Toast.makeText(MainActivity.this,
                                "Error: " + error.getMessage(),
                                Toast.LENGTH_SHORT).show();
                    }
                }
        );

        sr.setTag(this);
        requestQueue.add(sr);
    }
}

What's been fixed and improved:

  • Removed the unused AsyncTask import. The original imported android.os.AsyncTask but never used it.
  • Removed the unused TextView import. No TextView is used in MainActivity.
  • Removed the unused Type / TypeToken imports. The original didn't use Gson's type tokens. But I've added a cleaner version below using them, so the imports are actually correct now.
  • Reused the RequestQueue. The original created a new one on every tap: Volley.newRequestQueue(MainActivity.this).add(sr). That leaks threads. Now it's a single field created in onCreate.
  • Cancelled pending requests in `onDestroy`. Prevents callbacks from firing on a destroyed activity.
  • Cleared the list before repopulating. If the user taps the button twice, the list is reset so you don't get duplicate rows.
  • Wrapped JSON parsing in try/catch. If the API changes or returns malformed data, the app shows a toast instead of crashing.
  • Switched to the new `disease.sh` API. The original endpoint is dead.
  • Better dialog. The original only showed the death count. Now it shows every field the API provides.
  • Consistent message text. The original mixed "Total Death" (singular) with "Total Cases". Standardised everything.

A Cleaner Alternative: Parse the Whole Array with Gson

If you prefer, you can skip the manual JSONArray loop entirely and let Gson parse the whole list in one line using a TypeToken:

Gson gson = new Gson();
Type listType = new TypeToken<List<Case>>(){}.getType();
List<Case> parsed = gson.fromJson(response, listType);

caseList.clear();
caseList.addAll(parsed);
customCaseAdapter.notifyDataSetChanged();

This is cleaner and faster — Gson knows how to build a List<Case> directly from the JSON array. The TypeToken is necessary because Java erases generics at runtime, so Gson needs to know the element type at parse time.

I've kept the manual JSONArray loop in the main example because the original tutorial used it — but the TypeToken version is what you'd write in production.

How the Code Works

Let's trace the flow when the user taps "Load All Countries".

1. Setup in onCreate

We wire up the RecyclerView with a GridLayoutManager set to 2 columns. This is what makes the output a grid rather than a vertical list. We also create the adapter, attach the click listener, and build the single RequestQueue.

2. Button Tap Fires getData()

The android:onClick="getData" attribute in the layout wires the button directly to the method. We show a spinner, clear the list, and start the request.

3. Volley Fetches the JSON Array

Volley sends a GET request to https://disease.sh/v3/covid-19/countries. The response is a JSON array with one object per country.

4. Gson Parses Each Object

We wrap the response in a JSONArray, loop through it, and use gson.fromJson() on each element to produce a Case object. This builds up the caseList one object at a time.

5. Adapter Refreshes the Grid

After the loop, we call notifyDataSetChanged(). RecyclerView uses the adapter's getItemCount() and onBindViewHolder() to render each country as a card in the grid.

6. Card Tap Opens a Dialog

The adapter forwards tap events through its CaseListener interface. In MainActivity, the listener implementation reads the tapped Case object and shows an AlertDialog with all its fields.

What the API Returns

For https://disease.sh/v3/covid-19/countries, the response looks like:

[
  {
    "country": "India",
    "cases": 45000000,
    "todayCases": 1200,
    "deaths": 530000,
    "todayDeaths": 15,
    "recovered": 44500000,
    "active": 24000,
    "critical": 700,
    ...
  },
  {
    "country": "USA",
    "cases": 100000000,
    ...
  },
  ...
]

Common Pitfalls

  • Assuming the API endpoint will stay alive. The original tutorial's endpoint died when Heroku ended free dynos in 2022. If your app suddenly stops working, check the endpoint first.
  • Creating a new RequestQueue on every request. This leaks threads and network connections. Use a single queue per activity (or a singleton queue for the whole app).
  • Not cancelling requests on activity destroy. If the user leaves the screen while a request is in-flight, the callback fires on a destroyed activity. Use setTag() + cancelAll().
  • Forgetting to clear the list. If the user taps the fetch button twice, caseList doubles in size. Always clear it before repopulating.
  • Adding a log call inside `onBindViewHolder`. It fires on every scroll and floods Logcat. Only log when something is actually wrong.
  • Using the deprecated `getAdapterPosition()`. Use getBindingAdapterPosition() instead. It handles the case where the item has been removed from the list.
  • Null case listener. If setCaseListener is never called, tapping a card crashes on a null pointer. Always check for null before calling.
  • Assuming a `uses-permission-sdk-23` is right for INTERNET. That element is only for runtime permissions. INTERNET is granted at install time with a plain uses-permission.
  • Not handling non-200 responses. Volley's error listener fires for network errors, but a 500 response with valid body content still goes to onResponse. Consider checking response content or using a JsonObjectRequest with a status code check.
  • Parsing with `int` when the API might return `long`. Case counts for large countries can exceed 2 billion. Consider using long instead of int to avoid overflow on the largest values.
  • Sending too many items to `notifyDataSetChanged()`. For large lists (200+ countries), that's fine, but for thousands of items, use DiffUtil or ListAdapter for smooth updates.

Wrapping Up

You now have a working Android app that fetches data for every country from a live API, parses it with Gson, and displays it in a scrollable grid of cards. Tapping a card opens a dialog with detailed stats.

From here, you can extend the example with:

  • Search box. Add an EditText above the grid to filter countries as the user types.
  • Sort options. Sort by name, total cases, or deaths using a menu or spinner.
  • Pull to refresh. Wrap the RecyclerView in a SwipeRefreshLayout to let users reload.
  • Detail screen instead of dialog. Replace the AlertDialog with a full activity showing charts and history.
  • Offline caching. Store the last response in Room and show it when the network fails.
  • Retrofit + Moshi. Retrofit is the modern standard for Android HTTP requests and pairs well with Moshi (a Gson alternative with better Kotlin support).
  • Coroutines. If you migrate to Kotlin, replace the Volley callbacks with suspend functions and Kotlin coroutines.

6 comments:

MH Zibon said...

Very good written article. It will be supportive to anyone who utilizes it, including me. Keep doing what you are doing – can't wait to read more posts.the complete guide

Anonymous said...

I am so much excited after reading your blog. Your blog is very much innovative and much helpful for any industry as well as in person.

flutter programmierer

Buy Online said...


Being cute is a way of life for this lively charmer. The Shih Tzu is known to be especially affectionate with children. As a small dog bred to spend most of their day inside royal palaces, they make a great pet if you live in an apartment or lack a big backyard.
where to buy Shih Tzu puppies

Buy Online said...


We offer great deals and expert service.Our specialty are on gun brands like the COLT,which include,the colt rifles,revolvers,colt pistols,Taurus Handguns and Panzer arms.We have the latest collection of the AR-12 Shotguns,panzer bp-12,panzer arms ar12 pro. USA GUN FACTORY is a federally licensed firearms dealer, Visit now

Taurus Tracker

Anonymous said...

I seriously love your blog.. Pleasant colors & theme.
Did you build this amazing site yourself? Please reply back as I’m trying to create my own personal blog and would like to
learn where you got this from or what the theme is named. Many thanks!
order muha meds carts online

Anonymous said...

I quite like reading through an article that can make men and women think.
Also, many thanks for allowing for me to comment!
Buy Vyvanse Online cheap and affordable with or without prescription