Membuat endless scrolling pada recyclerview

Photo by Rami Al-zayat on Unsplash

Dalam membuat sebuah aplikasi, biasanya kita dihadapkan pada banyaknya data yang ditampilkan dalam aplikasi atau kepada user.  jika salah dalam menerapkan Kodingmisalnya data yang ditampilkan pada user diload seluruhnya, tentu ini tidak bagus karena akan memperlambat data yang akan ditampilkan, bayangkan jika data tersebut ada 1 juta dan harus diload seluruhnya, MBLEDOS !!

Untuk mengatasi ini, kita perlu menggunakan pagination atau kalau dalam pemrograman android disebut dengan istilah Endless scrolling. intinya, kita akan membatasi jumlah data yang ditampilkan ke user. Misalnya, jumlah yang akan ditampilkan per-20 data kemudian ketika user menscroll aplikasi setelah data ke-20, maka akan muncul progress bar dan data selanjutkan akan ditampilkan sebanyak 20. Kita gunakan recyclerview dan cardview untuk menampung data pada tutorial ini.

Setting gradle

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    implementation 'com.android.support:design:26.1.0'
    compile 'com.android.support:recyclerview-v7:26.1.0'
    compile 'com.android.support:cardview-v7:26.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

Pada tutorial ini kita gunakan 5 layout, mari buat layout untuk activity_main.xml, layout ini berfungsi untuk menampilkan toolbar sekaligus menampung content_main.xml.

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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"
   tools:context="com.dekikurnia.scrolling.MainActivity">
 
    <android.support.design.widget.AppBarLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:theme="@style/AppTheme.AppBarOverlay">
 
        <android.support.v7.widget.Toolbar
           android:id="@+id/toolbar"
           android:layout_width="match_parent"
           android:layout_height="?attr/actionBarSize"
           android:background="?attr/colorPrimary"
           app:popupTheme="@style/AppTheme.PopupOverlay" />
 
    </android.support.design.widget.AppBarLayout>
 
    <include layout="@layout/content_main" />
 
</android.support.design.widget.CoordinatorLayout>

Selanjutnya buat layout content_main.xml,  layout ini berfungsi untuk menampung adapter_contact.xml dalam FrameLayout.

<?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"
   app:layout_behavior="@string/appbar_scrolling_view_behavior">
 
    <FrameLayout
       android:id="@+id/content_frame"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:layout_alignParentTop="true"
       android:layout_alignParentStart="true"
       android:layout_alignParentLeft="true">
        <include layout="@layout/adapter_contact"></include>
    </FrameLayout>
 
</RelativeLayout>

Setelah membuat layout untuk activity dan content, kita buat layout untuk adapter, buat layout baru dengan nama file adapter_content.xml. Kita gunakan Recyclerview nantinya untuk menampung Cardview.

<?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="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">
 
    <android.support.v7.widget.RecyclerView
       android:id="@+id/recycler_view"
       android:layout_width="match_parent"
       android:layout_height="wrap_content" />
</RelativeLayout>

Selanjutnya, kita buat layout untuk menampilkan datanya. Buat layout dengan nama list_contact.xml, layout ini menggunakan Cardview untuk menampung Textview. Dan tidak lupa kita buat layout untuk progressbarnya.

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:card_view="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   card_view:cardUseCompatPadding="true">
 
    <RelativeLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:background="?android:selectableItemBackground"
       android:padding="10dp">
 
        <TextView
           android:id="@+id/txt_email"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:textColor="@android:color/black"
           android:textSize="16sp"
           android:textStyle="bold" />
 
        <TextView
           android:id="@+id/txt_phone"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:layout_below="@id/txt_email"
           android:textColor="@android:color/black"
           android:textSize="12sp" />
    </RelativeLayout>
</android.support.v7.widget.CardView>
<?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:orientation="vertical">
 
    <ProgressBar
       android:id="@+id/progressBar1"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_gravity="center_horizontal" />
 
</LinearLayout>

Oke, kita telah menyelesaikan untuk tampilannya. Selanjutnya, kita buat untuk bagian koding java-nya. Mari kita buat class POJO. Contoh disini kita akan buat class Contact dimana dalam class tersebut kita buat 2 field yaitu email dan phone dengan masing-masing menggunakan tipe data String.

package com.dekikurnia.scrolling;
 
/**
 * Created by dekikurnia on 12/12/2017.
 */
 
public class Contact {
    private String email;
    private String phone;
 
 
    public String getEmail() {
        return email;
    }
 
    public void setEmail(String email) {
        this.email = email;
    }
 
    public String getPhone() {
        return phone;
    }
 
    public void setPhone(String phone) {
        this.phone = phone;
    }
}

Kemudian, buat interface dengan nama OnLoadMoreListener yang didalamnya terdapat abstract method onLoadMore()

package com.dekikurnia.scrolling;
 
/**
 * Created by dekikurnia on 12/12/2017.
 */
 
public interface OnLoadMoreListener {
    void onLoadMore();
}

Konfigurasi RecyclerView  adapter

Pada bagian ini kita buat class RecyclerView adapter, kita buat dengan nama ContactAdapter. pada bagian ini kita buat 2 tempat untuk menampung item yang telah kita buat sebelumnya di layout, yaitu untuk ProgressBar dan untuk TextView. Masing-masing kita buat class dengan nama LoadingViewHolder dan UserViewHolder.

private class LoadingViewHolder extends RecyclerView.ViewHolder {
        public ProgressBar progressBar;
 
        public LoadingViewHolder(View view) {
            super(view);
            progressBar = (ProgressBar) view.findViewById(R.id.progressBar1);
        }
    }
 
    private class UserViewHolder extends RecyclerView.ViewHolder {
        public TextView phone;
        public TextView email;
 
        public UserViewHolder(View view) {
            super(view);
            phone = (TextView) view.findViewById(R.id.txt_phone);
            email = (TextView) view.findViewById(R.id.txt_email);
        }
    }

Kita definisikan 2 buat konstata yaitu VIEW_TYPE_ITEM dan VIEW_TYPE_LOADING. Kemudian instansiasi OnLoadMoreListenerdan kita buat singleton.

private final int VIEW_TYPE_ITEM = 0;
private final int VIEW_TYPE_LOADING = 1;
 
private OnLoadMoreListener onLoadMoreListener;
public void setOnLoadMoreListener(OnLoadMoreListener mOnLoadMoreListener) {
     this.onLoadMoreListener = mOnLoadMoreListener;
}

Untuk menghandle event scroll pada RecyclerView kita buat didalam konstruktor adapter class. Buat method linearLayoutManager pada RecyclerView. Logikanya, jika scroll sudah mencapai batas data yang ditampilkan maka event progressBar dimunculkan.

private boolean isLoading;
private Activity activity;
private List<Contact> contacts;
private int visibleThreshold = 5;
private int lastVisibleItem, totalItemCount;
 
public ContactAdapter(RecyclerView recyclerView, List<Contact> contacts, Activity activity) {
     this.contacts = contacts;
     this.activity = activity;
 
     final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
     recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
         @Override
         public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
             super.onScrolled(recyclerView, dx, dy);
             totalItemCount = linearLayoutManager.getItemCount();
             lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
             if (!isLoading && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
                 if (onLoadMoreListener != null) {
                     onLoadMoreListener.onLoadMore();
                 }
                 isLoading = true;
             }
         }
     });
 }

Pada bagian RecyclerView.Holder, kita buat 2 buah logika jika viewType == VIEW_TYPE_ITEM maka membubungkan dengan layout list_contact, sedangkan jika viewType == VIEW_TYPE_LOADING maka akan membubungkan dengan layout item_loading

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
      if (viewType == VIEW_TYPE_ITEM) {
          View view = LayoutInflater.from(activity).inflate(R.layout.list_contact, parent, false);
          return new UserViewHolder(view);
      } else if (viewType == VIEW_TYPE_LOADING) {
          View view = LayoutInflater.from(activity).inflate(R.layout.item_loading, parent, false);
          return new LoadingViewHolder(view);
      }
      return null;
}

Berikut kode lengkap untuk ContactAdapter :

package com.dekikurnia.scrolling;
 
import android.app.Activity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
 
import java.util.List;
 
/**
 * Created by dekikurnia on 12/12/2017.
 */
 
public class ContactAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
 
    private final int VIEW_TYPE_ITEM = 0;
    private final int VIEW_TYPE_LOADING = 1;
    private OnLoadMoreListener onLoadMoreListener;
    private boolean isLoading;
    private Activity activity;
    private List<Contact> contacts;
    private int visibleThreshold = 5;
    private int lastVisibleItem, totalItemCount;
 
    public ContactAdapter(RecyclerView recyclerView, List<Contact> contacts, Activity activity) {
        this.contacts = contacts;
        this.activity = activity;
 
        final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                totalItemCount = linearLayoutManager.getItemCount();
                lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
                if (!isLoading && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
                    if (onLoadMoreListener != null) {
                        onLoadMoreListener.onLoadMore();
                    }
                    isLoading = true;
                }
            }
        });
    }
 
    public void setOnLoadMoreListener(OnLoadMoreListener mOnLoadMoreListener) {
        this.onLoadMoreListener = mOnLoadMoreListener;
    }
 
    @Override
    public int getItemViewType(int position) {
        return contacts.get(position) == null ? VIEW_TYPE_LOADING : VIEW_TYPE_ITEM;
    }
 
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == VIEW_TYPE_ITEM) {
            View view = LayoutInflater.from(activity).inflate(R.layout.list_contact, parent, false);
            return new UserViewHolder(view);
        } else if (viewType == VIEW_TYPE_LOADING) {
            View view = LayoutInflater.from(activity).inflate(R.layout.item_loading, parent, false);
            return new LoadingViewHolder(view);
        }
        return null;
    }
 
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof UserViewHolder) {
            Contact contact = contacts.get(position);
            UserViewHolder userViewHolder = (UserViewHolder) holder;
            userViewHolder.phone.setText(contact.getEmail());
            userViewHolder.email.setText(contact.getPhone());
        } else if (holder instanceof LoadingViewHolder) {
            LoadingViewHolder loadingViewHolder = (LoadingViewHolder) holder;
            loadingViewHolder.progressBar.setIndeterminate(true);
        }
    }
 
    @Override
    public int getItemCount() {
        return contacts == null ? 0 : contacts.size();
    }
 
    public void setLoaded() {
        isLoading = false;
    }
 
    private class LoadingViewHolder extends RecyclerView.ViewHolder {
        public ProgressBar progressBar;
 
        public LoadingViewHolder(View view) {
            super(view);
            progressBar = (ProgressBar) view.findViewById(R.id.progressBar1);
        }
    }
 
    private class UserViewHolder extends RecyclerView.ViewHolder {
        public TextView phone;
        public TextView email;
 
        public UserViewHolder(View view) {
            super(view);
            phone = (TextView) view.findViewById(R.id.txt_phone);
            email = (TextView) view.findViewById(R.id.txt_email);
        }
    }
}

Terakhir, kita buat koding untuk MainActivity-nya. dalam method onCreate() panggil setOnLoadMoreListener() dan menampilkan data baru setelah memanggil event progressBar didalam onLoadMore(). pada kode MainActivity, logikanya adalah kita buat dulu dummy data sebanyak 20 kemudian saat user menscroll sampai data ke-20 maka akan muncul progressBar dengan waktu delay 5000ms. Lalu, saat user menscroll sampai data terakhir maka aplikasi akan memberi pesan “Loading data completed”. Selebihnya, silahkan diset/diatur sesuai kebutuhan waktu delay dan data yang akan ditampilkan pertama sebanyak berapa pada user.

 private List<Contact> contacts;
 private ContactAdapter contactAdapter;
 private Random random;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
 
        contacts = new ArrayList<>();
        random = new Random();
 
        for (int i = 0; i < 20; i++) {
            Contact contact = new Contact();
            contact.setPhone("085710830260");
            contact.setEmail("dekikurnia" + i + "@gmail.com");
            contacts.add(contact);
        }
 
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
 
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        contactAdapter = new ContactAdapter(recyclerView, contacts, this);
        recyclerView.setAdapter(contactAdapter);
 
        contactAdapter.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore() {
                if (contacts.size() <= 20) {
                    contacts.add(null);
                    contactAdapter.notifyItemInserted(contacts.size() - 1);
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            contacts.remove(contacts.size() - 1);
                            contactAdapter.notifyItemRemoved(contacts.size());
 
                            int index = contacts.size();
                            int end = index + 10;
                            for (int i = index; i < end; i++) {
                                Contact contact = new Contact();
                                contact.setPhone("085710830260");
                                contact.setEmail("dekikurnia" + i + "@gmail.com");
                                contacts.add(contact);
                            }
                            contactAdapter.notifyDataSetChanged();
                            contactAdapter.setLoaded();
                        }
                    }, 5000);
                } else {
                    Toast.makeText(MainActivity.this, "Loading data completed", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

Untuk source code lengkapnya bisa diakses di https://github.com/dekikurnia/Endless-Scrolling

Post Author: dekikurnia