Please Like our Facebook Page

Android Endless Scrolling RecyclerView


Today, our walk through will be on how to implement endless scrolling RecyclerView. I have been doing research on this topic, until I achieved a smart yet a simple way on how to implement this behavior. Generally, you might have found some apps like Reddit, Facebook and Gmail apps implementing this behavior, the same thing they do, is the same thing we are going to achieve under this tutorial

Introduction to endless scrolling RecyclerView

Achieving endless scrolling ReccyelrView is quite easy. All we need to do is do understand when user is scrolling down and try to understand when user reaches at the bottom of the RecyclerView. Please have a look at the code below as it explains how to detect when user is scrolling downwards or upwards of the recyclerview.

RecyclerView rv = (RecyclerView)findViewById(R.id.rv);

rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

        if (dy < 0) {
            // Recycle view scrolling up...

        } else if (dy > 0) {
            // Recycle view scrolling down...
        }
    }
});

This code introduces us the magic of the day. We will ignore onScrollStateChanged(RecyclerView recyclerView, int newState) method, as we don’t need it here. We will also ignore when user is scrolling upwards and concentrate only when user is scrolling downwards. You might have noticed something, our code above only tell us when user is scrolling downwards but it does not tell us when user reaches the bottom of recyclerView. How do we do that? Fortunately, RecyclerView has some build in methods and fields that will help us detect every time user reaches the bottom. Please look at the code below:


RecyclerView rv = (RecyclerView)findViewById(R.id.rv);

rv.addOnScrollListener(new RecyclerView.OnScrollListener() {

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

        if (dy > 0) {
            // Recycle view scrolling down...
            if(recyclerView.canScrollVertically(RecyclerView.FOCUS_DOWN) == false){
                Toast.makeText(getApplicationContext(), "Reached the end of recycler view", Toast.LENGTH_LONG).show();
            }

        } 
    }
});


!recyclerView.canScrollVertically(RecyclerView.FOCUS_DOWN) makes us detect when user has reached the bottom of the RecyclerView. Remeber we can proudly replace false with ! without having to change any meaning to the statement

You may also like:


Endless Scrolling RecyclerView Concept and Coding

Now we may start our main business of the day. To load more we will be loading data from a php page. Our json feed is available here https://hacksmile.com/hack_smile_tutorials/loadmore.php?limit=15. Please note to understand about limit, you must first study java and php code. Feel free to test with this json feed, it contains 250 objects which is more than enough for this tutorial. Please watch the following video to get the full concept.

Android Project

We will start with android development and then go to php. Please study code carefully. Now create new android project and name it almost anything you want. I will call mine New Application. We just need one activity. Having done that, please add volley gradle dependencies. For this tutorial I will not be using volley singleton so as to make sure no one is left behind.

    compile 'com.android.volley:volley:1.0.0'

Permissions
Please add internet permissions to your android manifest file.

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

content_main.xml
This is layout of project’s MainActivity.java

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    xmlns:wheel="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="3dp"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="hacksmile.com.MainActivity"
    tools:showIn="@layout/activity_main">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/loadmore_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/progress_wheel"
        android:scrollbars="vertical" />

    <!-- We need this loading wheel, only when loading more -->
    <com.pnikosis.materialishprogress.ProgressWheel
        android:id="@+id/progress_wheel"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:visibility="gone"
        wheel:matProg_barColor="@color/colorPrimary"
        wheel:matProg_progressIndeterminate="true" />


</RelativeLayout>

custom_row_layout.xml
This is RecyclerView’s custom row. Each row will be represented by this layout.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="90dp"
    android:layout_marginBottom="1dp"
    android:background="#fff"
    android:orientation="vertical">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginBottom="10dp"
        android:layout_marginLeft="16dp"
        android:layout_marginStart="16dp"
        android:text="@string/test_title"
        android:textColor="@color/colorPrimary"
        android:textSize="17sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/description"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/title"
        android:layout_centerVertical="true"
        android:layout_marginLeft="16dp"
        android:layout_marginStart="16dp"
        android:text="@string/test_description"
        android:textColor="#000" />

</RelativeLayout>

MainActivity.java

package hacksmile.com;

import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Toast;

import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;
import com.android.volley.toolbox.Volley;
import com.pnikosis.materialishprogress.ProgressWheel;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    // we will be loading 15 items per page or per load
    // you can change this to fit your specifications.
    // When you change this, there will be no need to update your php page,
    // as php will be ordered what to load and limit by android java
    private static final int LOAD_LIMIT = 15;

    // last id to be loaded from php page,
    // we will need to keep track or database id field to know which id was loaded last
    // and where to begin loading
    private String lastId = "0"; // this will issued to php page, so no harm make it string

    // we need this variable to lock and unlock loading more
    // e.g we should not load more when volley is already loading,
    // loading will be activated when volley completes loading
    private boolean itShouldLoadMore = true;

    // initialize adapter and data structure here
    private RecyclerAdapter recyclerAdapter;
    private ArrayList<RecyclerModel> recyclerModels;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);

        // you must assign all objects to avoid nullPointerException
        recyclerModels = new ArrayList<>();
        recyclerAdapter = new RecyclerAdapter(recyclerModels);

        RecyclerView recyclerView = (RecyclerView) this.findViewById(R.id.loadmore_recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setHasFixedSize(true);

        //we can now set adapter to recyclerView;
        recyclerView.setAdapter(recyclerAdapter);

        // create a function for the first load
        firstLoadData();

        // here add a recyclerView listener, to listen to scrolling,
        // we don't care when user scrolls upwards, will only be careful when user scrolls downwards
        // this listener is freely provided for by android, no external library
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

            // for this tutorial, this is the ONLY method that we need, ignore the rest
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                if (dy > 0) {
                    // Recycle view scrolling downwards...
                    // this if statement detects when user reaches the end of recyclerView, this is only time we should load more
                    if (!recyclerView.canScrollVertically(RecyclerView.FOCUS_DOWN)) {
                        // remember "!" is the same as "== false"
                        // here we are now allowed to load more, but we need to be careful
                        // we must check if itShouldLoadMore variable is true [unlocked]
                        if (itShouldLoadMore) {
                            loadMore();
                        }
                    }

                }
            }
        });

        setSupportActionBar(toolbar);
    }

    // this function will load 15 items as indicated in the LOAD_LIMIT variable field
    private void firstLoadData() {

        String url = "https://hacksmile.com/hack_smile_tutorials/loadmore.php?limit=" + LOAD_LIMIT;
        // to make you understand everything, to the php page, we will be doing something like this
        // $limit = $_GET['limit']
        // then [SELECT * FROM table_name ORDER_BY id DESC LIMIT $limit ]

        itShouldLoadMore = false; // lock this guy,(itShouldLoadMore) to make sure,
        // user will not load more when volley is processing another request
        // only load more when  volley is free

        final ProgressDialog progressDialog = new ProgressDialog(this);
        progressDialog.setMessage("Loading...");
        progressDialog.setCancelable(false);
        progressDialog.show();


        JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(Request.Method.GET, url, null, new Response.Listener<JSONArray>() {
            @Override
            public void onResponse(JSONArray response) {
                progressDialog.dismiss();
                // remember here we are in the main thread, that means,
                //volley has finished processing request, and we have our response.
                // What else are you waiting for? update itShouldLoadMore = true;
                itShouldLoadMore = true;

                if (response.length() <= 0) {
                    // no data available
                    Toast.makeText(MainActivity.this, "No data available", Toast.LENGTH_SHORT).show();

                    return;
                }

                for (int i = 0; i < response.length(); i++) {
                    try {
                        JSONObject jsonObject = response.getJSONObject(i);

                        // please note this last id how we have updated it
                        // if there are 4 items for example, and we are ordering in descending order,
                        // then last id will be 1. This is because outside a loop, we will get the last
                        // value [Thanks to JAVA]

                        lastId = jsonObject.getString("id");
                        String title = jsonObject.getString("title");
                        String description = jsonObject.getString("description");

                        recyclerModels.add(new RecyclerModel(title, description));
                        recyclerAdapter.notifyDataSetChanged();

                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }

                // please note how we have updated our last id variable which is initially 0 (String)
                // outside the loop, java will return the last value, so here it will
                // certainly give us lastId that we need

            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                // also here, volley is not processing, unlock it should load more
                itShouldLoadMore = true;
                progressDialog.dismiss();
                Toast.makeText(MainActivity.this, "network error!", Toast.LENGTH_SHORT).show();
                new AlertDialog.Builder(MainActivity.this)
                        .setMessage(error.toString())
                        .show();
            }
        });

        Volley.newRequestQueue(this).add(jsonArrayRequest);


    }

    private void loadMore() {


        String url = "https://hacksmile.com/hack_smile_tutorials/loadmore.php?action=loadmore&lastId=" + lastId + "&limit=" + LOAD_LIMIT;
        // our php page starts loading from 250 to 1, because we have [ORDER BY id DESC]
        // So until you clearly understand everything, for this tutorial use ORDER BY ID DESC
        // so we will do something like this to the php page
        //==============================================
        // $limit = $_GET['limit']
        // $lastId = $_GET['lastId']
        // then [SELECT * FROM table_name WHERE id < $lastId ORDER_BY id DESC LIMIT $limit ]
        // here we shall load 15 items from table where lastId id less than last loaded id

        // if you are using [ASC] in sql, your query might change to tis
        // then [SELECT * FROM table_name WHERE id > $lastId ORDER_BY id DESC LIMIT $limit ]
        // for this tutorial let's stick to [DESC]


        itShouldLoadMore = false; // lock this until volley completes processing

        // progressWheel is just a loading spinner, please see the content_main.xml
        final ProgressWheel progressWheel = (ProgressWheel) this.findViewById(R.id.progress_wheel);
        progressWheel.setVisibility(View.VISIBLE);

        JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(Request.Method.GET, url, null, new Response.Listener<JSONArray>() {
            @Override
            public void onResponse(JSONArray response) {
                progressWheel.setVisibility(View.GONE);

                // since volley has completed and it has our response, now let's update
                // itShouldLoadMore

                itShouldLoadMore = true;

                if (response.length() <= 0) {
                    // we need to check this, to make sure, our dataStructure JSonArray contains
                    // something
                    Toast.makeText(MainActivity.this, "no data available", Toast.LENGTH_SHORT).show();
                    return; // return will end the program at this point
                }

                for (int i = 0; i < response.length(); i++) {
                    try {
                        JSONObject jsonObject = response.getJSONObject(i);

                        // please note how we have updated the lastId variable
                        // if there are 4 items for example, and we are ordering in descending order,
                        // then last id will be 1. This is because outside a loop, we will get the last
                        // value

                        lastId = jsonObject.getString("id");
                        String title = jsonObject.getString("title");
                        String description = jsonObject.getString("description");

                        recyclerModels.add(new RecyclerModel(title, description));
                        recyclerAdapter.notifyDataSetChanged();

                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }

            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                progressWheel.setVisibility(View.GONE);
                // volley finished and returned network error, update and unlock  itShouldLoadMore
                itShouldLoadMore = true;
                Toast.makeText(MainActivity.this, "Failed to load more, network error", Toast.LENGTH_SHORT).show();

            }
        });

        Volley.newRequestQueue(this).add(jsonArrayRequest);

    }


}

RecyclerModel.java



This is RecyclerView Adapter data model.

package com.hacksmile.tutorials;


public class RecyclerModel {
    private String title;
    private String description;

    public RecyclerModel(String title, String description) {
        this.title = title;
        this.description = description;
    }

    public String getTitle() {
        return title;
    }

    public String getDescription() {
        return description;
    }
}

RecyclerAdapter.java
Our recyclerView Adapter

package hacksmile.com;


import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;

class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.MyViewHolder> {

    private ArrayList<RecyclerModel> recyclerModels; // this data structure carries our title and description

    public RecyclerAdapter(ArrayList<RecyclerModel> recyclerModels) {
        this.recyclerModels = recyclerModels;
    }

    @Override
    public RecyclerAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // inflate your custom row layout here
        return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.custom_row_layout, parent, false));
    }

    @Override
    public void onBindViewHolder(RecyclerAdapter.MyViewHolder holder, int position) {
        // update your data here

        holder.title.setText(recyclerModels.get(position).getTitle());
        holder.description.setText(recyclerModels.get(position).getDescription());

    }

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

    class MyViewHolder extends RecyclerView.ViewHolder {
        // view this our custom row layout, so intialize your variables here
        private TextView title;
        private TextView description;

        MyViewHolder(View view) {
            super(view);

            title = (TextView) view.findViewById(R.id.title);
            description = (TextView) view.findViewById(R.id.description);

        }
    }
}

PHP CODE

This section will look at the php server side code. All load more manipulation will take place here. If you studied java code correctly, and especially the comments inside the code, you will get this quickly.

function loadData($limit){
	require "config.inc.php";

	$query = $con->prepare("SELECT * FROM loadmore ORDER BY id DESC LIMIT $limit "); 
	$query->execute();
	$array = array(); 

	while($data = $query->fetch(PDO::FETCH_ASSOC)){
		$id = $data['id'];
		$title = $data['title']; 
		$description = $data['description'];
		array_push($array, array(
				"id" => $id,
				"title" => $title, 
				"description" => $description
			)
		);
}

echo json_encode($array);


}

function loadMoreData($lastId, $limit){
	require "config.inc.php";
	try{
		$query = $con->prepare("SELECT * FROM loadmore WHERE id < $lastId ORDER BY id DESC LIMIT $limit "); $query->execute();
		$array = array(); 

		while($data = $query->fetch(PDO::FETCH_ASSOC)){
			$id = $data['id'];
			$title = $data['title']; 
			$description = $data['description'];
			array_push($array, array(
					"id" => $id,
					"title" => $title, 
					"description" => $description
				)
			);
		}

		echo json_encode($array);
	} catch(Exception $e){
		die($e->getMessage());
	}
}



if(isset($_GET['action']) && $_GET['action'] == "loadmore"){
	$lastId = $_GET['lastId'];
	// this is teh limit set in the android java code (LOAD_LIMIT)
	$limit = $_GET['limit'];
	loadMoreData($lastId, $limit);
} else {
	$limit = $_GET['limit'];
	loaddata($limit);
}

config.inc.php

<?php

$servername = "localhost";
$username = "root";
$passwordData = "";
$dbname = "hackmsile_tutorials";

try {
	$con = new PDO("mysql:host=$servername;dbname=$dbname", $username, $passwordData);
	$con->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(PDOException $e) {
    die("Database error due to: ".$e->getMessage());
}


Conclusion



This marks the end of our tutorial. In case you have any question, you can ask me in the commenting system below. Code and apk will be shared later. Thanks










Please Like us on Facebook