Android SQLite - RecyclerView - Infinite/Endless Pagination,Load More [ServerSide]

November 10, 2017 Oclemy Android SQLite, Android RecyclerView 11 minutes, 57 seconds

Hey guys.In this tutorial we continue looking at the SQlite Pagination. How to page/paginate data endlessly. We use the Load More technique. The user scrolls the RecyclerView,when he reaches the end of the list,we load more data from SQlite database.

We are performing our SQlite pagination at the server side. While loading our data using the endless scroll,we shall be displaying a ProgressBar as the data loads. Our RecyclerView comprises cardviews.

We first insert/save data into SQlite database via a dialog, then select the data and bind to our RecyclerView. We also see how to swipe/pull to refresh our data.

If you pull down.swipe down our recyclerView,we refresh our dataset and take us back to the first page of our data.We are suing the pullLoadView library.Here's what we do in short :

  • First Insert/Save data to SQlite database from a dialog with edittexts.
  • If the app is run,it tries to select/retrieve the first page of our dataset.
  • The first page comprises 5 items each displayed in a cardView.
  • We eager load the second page when you are in the first page.
  • When you scroll to the second page,we eager load the third page,then fourth when you are in the third page and so on.
  • While laoding more data,we show a progress bar at the bottom of our RecyclerView.
  • SQlite is fast compared to loading of data from a server. We shall load the data in the background thread using handlers.Now we are going to show our progressbar for three seconds even though our SQlite queries of fetching the five items in a page will be much faster than the 3 seconds.The purpose is to teach infinite scroll,so the three seconds shall make our progressbar visible and simulate a real world fetching of data from the server. Take note though that we are fetching data from an actual database in SQLite.
  • We are using RushORM for our SQLite database stuff.RushORM is a relational object mapper an shall map our model class into sqlite table without us writing sql statements.Moreover,our queries we shall perform in Java not SQL.

Project Demo

How to Run

  • Download the project above.
  • You'll get a zipped file,extract it.
  • Open the Android Studio.
  • Now close, already open project
  • From the Menu bar click on File >New> Import Project
  • Now Choose a Destination Folder, from where you want to import project.
  • Choose an Android Project.
  • Now Click on “OK“.
  • Done, your done importing the project,now edit it.

 

Our Gradle Scripts

There are two build.gradle files in our android studio project :

Build.Gradle (Project)

  • Our project level build.gradle, here we add the repository url for our RushORM.We fetch it from maven.

 

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.3'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        maven {
            url "http://maven.rushorm.com"
        }
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

     

Build.Gradle (App)

  • Here we add some of the dependencies we shallbe using,including third part dependencies.
  • RushORM and PullLoadVeiw are third party libraries.
  • RushORM is for our SQlite database while PullLoadView enables us implement Pull to Load More in our RecyclerView.

 

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.2.1'
    compile 'com.android.support:design:24.2.1'
    compile 'com.android.support:cardview-v7:24.2.1'
    compile 'co.uk.rushorm:rushandroid:1.2.0'
    compile 'com.github.tosslife:pullloadview:1.1.0'

}

     

Our Layouts

ContentMain.xml

  • Our Activity's Content Layout.

 

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.tutorials.hp.sqliteinfinitepager.MainActivity"
    tools:showIn="@layout/activity_main">

    <com.srx.widget.PullToLoadView
    android:id="@+id/pullToLoadView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    />
</RelativeLayout>

     

Model.xml

  • Our model layout.
  • We inflate this to our RecyclerView viewitems.
  • At the root we have a CardView.

 

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_margin="10dp"
    card_view:cardCornerRadius="10dp"
    card_view:cardElevation="10dp"
    android:layout_height="200dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:text="Name"
            android:id="@+id/nameTxt"
            android:padding="10dp"
            android:layout_alignParentLeft="true"
            />

    </RelativeLayout>
</android.support.v7.widget.CardView>

     

DialogLayout.xml

  • We are performing SQLite CRUD,so we need an input dialog with edittext and button.

 

<?xml version="1.0" encoding="utf-8"?>

<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="500dp"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_margin="1dp"
    card_view:cardCornerRadius="10dp"
    card_view:cardElevation="5dp"
    android:layout_height="match_parent">

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

        <android.support.design.widget.TextInputLayout
            android:id="@+id/nameLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <EditText
                android:id="@+id/nameEditTxt"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:singleLine="true"
                android:hint= "Name" />
        </android.support.design.widget.TextInputLayout>

        <Button android:id="@+id/saveBtn"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="Save"
            android:clickable="true"
            android:background="@color/colorAccent"
            android:layout_marginTop="40dp"
            android:textColor="@android:color/white"/>

    </LinearLayout>

</android.support.v7.widget.CardView>

     

Our Classes

We have a total of 5 classes :

MainApplication

  • Extends our Application class.
  • Given its global,we take advantage and initialize our RushORM here.

 

package com.tutorials.hp.sqliteinfinitepager;

import android.app.Application;

import co.uk.rushorm.android.AndroidInitializeConfig;
import co.uk.rushorm.core.RushCore;

public class MainApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        //INITIALIZE RUSHCORE CONFIGURATION
        AndroidInitializeConfig config=new AndroidInitializeConfig(getApplicationContext());
        RushCore.initialize(config);
    }
}

     

Spaceship

  • Is our model class, to represent a spaceship with properties like name.
  • We'll map it to RushORM by supplying an empty constructor and deriving from RushObject.

 

package com.tutorials.hp.sqliteinfinitepager.mData;

import co.uk.rushorm.core.RushObject;

public class Spaceship extends RushObject {
    private String name;

    public Spaceship() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

     

MyAdapter

  • To adapt our dataset to the corresponding views.
  • We inflate our Model.xml layout here.
  • We also have an inner viewholder class.

 

package com.tutorials.hp.sqliteinfinitepager.mRecycler;

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

import com.tutorials.hp.sqliteinfinitepager.R;
import com.tutorials.hp.sqliteinfinitepager.mData.Spaceship;

import java.util.List;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyHolder> {

    Context c;
    List<Spaceship> spaceships;

    /*
    CONSTRUCTOR
     */
    public MyAdapter(Context c, List<Spaceship> spaceships) {
        this.c = c;
        this.spaceships = spaceships;
    }

    //INITIALIE VH
    @Override
    public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.model, parent, false);
        MyHolder holder = new MyHolder(v);
        return holder;
    }

    //BIND DATA
    @Override
    public void onBindViewHolder(MyHolder holder, int position) {
        holder.nametxt.setText(spaceships.get(position).getName());

    }

    /*
    TOTAL ITEMS
     */
    @Override
    public int getItemCount() {
        return spaceships.size();

    }

    /*
    ADD DATA TO ADAPTER
     */
    public void add(Spaceship s) {
        spaceships.add(s);
        notifyDataSetChanged();
    }

    /*
    CLEAR DATA FROM ADAPTER
     */
    public void clear() {
        spaceships.clear();
        notifyDataSetChanged();
    }

    /*
    VIEW HOLDER CLASS
     */
    class MyHolder extends RecyclerView.ViewHolder {

        TextView nametxt;

        public MyHolder(View itemView) {
            super(itemView);

            this.nametxt = (TextView) itemView.findViewById(R.id.nameTxt);

        }
    }

}

     

Paginator

  • This class shall page for us our SQLite data.
  • This is where we retrieve our SQlite data.We said we are performing server side pagination.
  • With RushORM we can use the limit() and offset() methods to set our limit and offset values.
  • We then set itour RecyclerView's adapter.

 

package com.tutorials.hp.sqliteinfinitepager.mData;

import android.content.Context;
import android.os.Handler;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.widget.Toast;

import com.srx.widget.PullCallback;
import com.srx.widget.PullToLoadView;
import com.tutorials.hp.sqliteinfinitepager.mRecycler.MyAdapter;

import java.util.ArrayList;
import java.util.List;

import co.uk.rushorm.core.RushSearch;

public class Paginator {

    Context c;
    private PullToLoadView pullToLoadView;
    RecyclerView rv;
    private MyAdapter adapter;
    private boolean isLoading = false;
    private boolean hasLoadedAll = false;
    private int nextPage;

    private int ITEMS_PER_PAGE;

   /*
   CONSTRUCTOR
    */
    public Paginator(Context c, PullToLoadView pullToLoadView,RecyclerView rv) {
        this.c = c;
        this.pullToLoadView = pullToLoadView;
        this.rv=rv;

        ITEMS_PER_PAGE=5;

        adapter = new MyAdapter(c, new ArrayList<Spaceship>());
        rv.setAdapter(adapter);

        initializePaginator();
    }

    /*
    PAGE DATA
     */
    public void initializePaginator() {
        pullToLoadView.isLoadMoreEnabled(true);
        pullToLoadView.setPullCallback(new PullCallback() {

            //LOAD MORE DATA
            @Override
            public void onLoadMore() {
                loadData(nextPage);
                Toast.makeText(c, "Load More", Toast.LENGTH_SHORT).show();
            }

            //REFRESH AND TAKE US TO FIRST PAGE
            @Override
            public void onRefresh() {
                adapter.clear();
                hasLoadedAll = false;
                loadData(0);
            }

            //IS LOADING
            @Override
            public boolean isLoading() {
                return isLoading;
            }

            //CURRENT PAGE LOADED
            @Override
            public boolean hasLoadedAllItems() {
                return hasLoadedAll;
            }
        });

        pullToLoadView.initLoad();
    }

    /*
    LOAD DATA.PASS CURRENT PAGE
     */
    public void loadData(final int page) {
        isLoading = true;
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {

                //ADD CURRENT PAGE TO OUR ADAPTER
                for(Spaceship spaceship : getCurrentSpacecrafts(page))
                {
                    adapter.add(spaceship);
                }
                //UPDATE PROPETIES
                pullToLoadView.setComplete();
                isLoading = false;
                nextPage = page + 1;
            }

        }, 3000);

    }

    /*
    CURRENT PAGE SPACECRAFTS LIST
     */
    public List<Spaceship> getCurrentSpacecrafts(int currentPage)
    {
        int startItem=currentPage*ITEMS_PER_PAGE;
        List<Spaceship> currentSpacecrafts=new ArrayList<>();
        try
        {
            //PAGE AT SERVER SIDE
            currentSpacecrafts=new RushSearch().limit(ITEMS_PER_PAGE).offset(startItem).find(Spaceship.class);

        }catch (Exception e)
        {
            e.printStackTrace();
        }
        return currentSpacecrafts;
    }

}

     

MainActivity

  • Our launcher activity.
  • We show initialize our dialog here and show it when user clicks the Floating Action Button.
  • WE then save to SQlite database with help of RushORM when user clicks the save button from edittext.
  • We initialize our PullLoadView here and use it to obtain a RecyclerView.

 

package com.tutorials.hp.sqliteinfinitepager;

import android.app.Dialog;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
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.view.Menu;
import android.view.MenuItem;
import android.widget.Button;
import android.widget.EditText;

import com.srx.widget.PullToLoadView;
import com.tutorials.hp.sqliteinfinitepager.mData.Paginator;
import com.tutorials.hp.sqliteinfinitepager.mData.Spaceship;
import com.tutorials.hp.sqliteinfinitepager.mRecycler.MyAdapter;

public class MainActivity extends AppCompatActivity {

    EditText nameEditText;
    Button saveBtn;
    RecyclerView rv;
    PullToLoadView pullToLoadView;

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

        pullToLoadView= (PullToLoadView) findViewById(R.id.pullToLoadView);

        //RECYCLERVIEW
        rv = pullToLoadView.getRecyclerView();
        rv.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));

        new Paginator(this,pullToLoadView,rv).initializePaginator();

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                displayDialog();
            }
        });
    }

    /*
   DISPLAY INPUT DIALOG
    */
    private void displayDialog()
    {
        final Dialog d=new Dialog(this);
        d.setTitle("SQLITE DATA");
        d.setContentView(R.layout.dialog_layout);

        nameEditText= (EditText) d.findViewById(R.id.nameEditTxt);
        saveBtn= (Button) d.findViewById(R.id.saveBtn);

        saveBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Spaceship s=new Spaceship();
                save(s);
                nameEditText.setText("");

            }
        });

        d.show();

    }

    /*
    INSERT DATA
     */
    private void save(Spaceship s)
    {
        s.setName(nameEditText.getText().toString());
        s.save();
    }

}

AndroidManiefst.xml

  • Given that we are using RusORM,lets complete our setup.You need to add metadata that RushORM shall use to  know you datavse meta information like database name.
  • But the most important thing is to make sure you specify the path to your model class by specifying the package name in the Rush_classes_package meta.For instance our model class is Spaceship so we add the package of our spaceship class.e.g

    <meta-data android:name="Rush\_classes\_package" android:value="com.tutorials.hp.sqliteinfinitepager.mData" />
  • Also regisetr you MainApplication by specifying the name in our tag. e. g

android:name=".MainApplication".

 

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

    <application
        android:name=".MainApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        <!--META DATAS FOR OUR DATABASE-->
        <!--THIS FIRST LINE IS COMPULSORY.ADD YOUR DATABASE PACKAGE-->
        <meta-data android:name="Rush_classes_package" android:value="com.tutorials.hp.sqliteinfinitepager.mData" />
        <!-- Updating this will cause a database upgrade -->
        <meta-data android:name="Rush_db_version" android:value="1" />

        <!-- Database name -->
        <meta-data android:name="Rush_db_name" android:value="SpaceshipDB.db" />

        <!-- Setting this to true will cause a migration to happen every launch,
        this is very handy during development although could cause data loss -->
        <meta-data android:name="Rush_debug" android:value="false" />

        <!-- Setting this to true mean that tables will only be created of classes that
        extend RushObject and are annotated with @RushTableAnnotation -->
        <meta-data android:name="Rush_requires_table_annotation" android:value="false" />

        <!-- Turning on logging can be done by settings this value to true -->
        <meta-data android:name="Rush_log" android:value="false" />

    </application>

</manifest>

   

Video

https://www.youtube.com/watch?v=aOwH_s-NE6Y

Screenshot

 

Comments