RecyclerView States & Databinding
Databinding provides an easier way to manage application with less code. let's start by network states and recyclerview items.
Test cases for an application item listing:
- Network not available
- Network Available but not found in result
- Item found and shown in listings
- Load More functionality
- Pull to refresh functionality
- recyclerview dividers
All above this can be managed easily with databinding. First I'm using evant RecyclerView databinding library here.
compile 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter:2.0.1' compile 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter-recyclerview:2.0.1' Basics: Things we need
- Common Recyclerview layout which will have no internet image, one message to show messages, recyclerview itself
- Common bindings of listeners
- List Binder with listeners
- Connect All things
- Implement
Common Recyclerview layout which will have no internet image, one message to show messages, recyclerview itself
So
- Framelayout as parent
- Add no internet and all messages in one linearlayout
- Progress bars for loadmore and loding
- Recyclerview
<FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="your background"> <LinearLayout android:id="@+id/llNoInternetConnection" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:orientation="vertical"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/no_internet_connection"/> <TextView style="@style/Button.selector_btn_green" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/try_again" android:clickable="true" android:layout_margin="@dimen/_15sdp" android:layout_gravity="center_horizontal" android:onClick="@{(v)->networkListener.onTryAgainClick()}"/> <TextView android:id="@+id/tvMessageText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="@{provider.message}"/> </LinearLayout> <!-- this will be shown at loading of recyclerview data no need to block ui --> <ProgressBar android:id="@+id/progressbar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.v4.widget.SwipeRefreshLayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/rvList" android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" android:paddingBottom="@dimen/_60sdp" android:layout_marginTop="@dimen/_5sdp"/> </android.support.v4.widget.SwipeRefreshLayout> </LinearLayout> <ProgressBar android:id="@+id/loadMoreProgress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|center_horizontal"/> </FrameLayout> Common bindings of listeners
public interface NetworkTryAgainListener { public void onTryAgainClick(); } public interface OnLoadMoreListener { void onLoadMore(); } Binding providers and states
There will be below possibility of recyclerview
- network error
- No items found for which we will be showing message
- Loading state
- Loadmore state
- Loaded state
public enum RecyclerviewErrorStateType { NetworkError,NoItemsFoundError,LoadingStarted,Loaded,LoadMore } While for swipe to refresh view we will be having 2 states
- Refreshing
- Refreshed
public enum SwipeRefreshStates { Refreshing,NotRefreshing } Needing one abstract class in which we manage states of recyclerview and swipe to refresh view
public abstract class ListBindingProviders<T> { //for recyclerview and progress bar states private final ObservableField<RecyclerviewErrorStateType> recyclerViewState = new ObservableField<>(RecyclerviewErrorStateType.LoadingStarted); //swipe layout states private final ObservableField<SwipeRefreshStates> swipeRefreshStates = new ObservableField<>(SwipeRefreshStates.NotRefreshing); //check if swipe refresh layout needed or not public boolean isSwipeRefreshEnabled = true; //check if loadmore needed or not public boolean isLoadMoreEnabled = false; //show error messages private String message; public abstract ObservableList<T> getItems(); public abstract ItemBinding<T> getItemBinding(); public void setSwipeRefreshEnabled(boolean swipeRefreshEnabled) { isSwipeRefreshEnabled = swipeRefreshEnabled; } public boolean isSwipeRefreshEnabled() { return isSwipeRefreshEnabled; } public void setLoadMoreEnabled(boolean loadMoreEnabled) { isLoadMoreEnabled = loadMoreEnabled; } public boolean isLoadMoreEnabled() { return isLoadMoreEnabled; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public ObservableField<RecyclerviewErrorStateType> getRecyclerViewState() { return recyclerViewState; } public void setRecyclerViewState(RecyclerviewErrorStateType recyclerviewErrorStateType) { recyclerViewState.set(recyclerviewErrorStateType); } public ObservableField<SwipeRefreshStates> getSwipeRefreshStates() { return swipeRefreshStates; } public void setSwipeRefreshStates(SwipeRefreshStates state) { swipeRefreshStates.set(state); } } Connect All things with xml
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:bind="http://schemas.android.com/tools"> <data> <import type="android.view.View"/> <import type="com.parthdave.recyclerviewbindinglibrary.bindings.ListBindingProviders"/> <import type="android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener"/> <import type="me.tatarka.bindingcollectionadapter2.LayoutManagers.LayoutManagerFactory"/> <import type="com.parthdave.recyclerviewbindinglibrary.states.RecyclerviewErrorStateType"/> <import type="com.parthdave.recyclerviewbindinglibrary.listeners.NetworkTryAgainListener"/> <import type="com.parthdave.recyclerviewbindinglibrary.listeners.OnLoadMoreListener"/> <variable name="provider" type="ListBindingProviders"/> <variable name="networkListener" type="NetworkTryAgainListener"/> <variable name="bindingDivider" type="String"/> <variable name="layoutManager" type="LayoutManagerFactory"/> <variable name="onRefreshListener" type="OnRefreshListener"/> <variable name="onLoadMoreListener" type="OnLoadMoreListener"/> </data> <FrameLayout ... > <LinearLayout android:id="@+id/llNoInternetConnection" ... android:visibility="@{provider.recyclerViewState==RecyclerviewErrorStateType.NetworkError?View.VISIBLE:View.GONE}"> <ImageView ... android:src="@drawable/no_internet_connection"/> <TextView ... android:onClick="@{(v)->networkListener.onTryAgainClick()}"/> <!-- To show error message --> <TextView ... android:text="@{provider.message}"/> </LinearLayout> <ProgressBar android:id="@+id/progressbar" ... android:visibility="@{provider.recyclerViewState==RecyclerviewErrorStateType.LoadingStarted?View.VISIBLE:View.GONE}"/> <LinearLayout ... android:visibility="@{(provider.recyclerViewState==RecyclerviewErrorStateType.Loaded || provider.recyclerViewState==RecyclerviewErrorStateType.LoadMore)?View.VISIBLE:View.GONE}"> <android.support.v4.widget.SwipeRefreshLayout ... bind:enabled="@{provider.isSwipeRefreshEnabled}" bind:onRefreshListener="@{onRefreshListener}" bind:refreshingState="@{provider.swipeRefreshStates}"> <android.support.v7.widget.RecyclerView ... app:itemBinding="@{provider.itemBinding}" app:items="@{provider.items}" app:layoutManager="@{layoutManager}" bind:dividers="@{bindingDivider}" bind:loadMoreEnabled="@{provider.loadMoreEnabled}" bind:onLoadMoreListener="@{onLoadMoreListener}"> </android.support.v7.widget.RecyclerView> </android.support.v4.widget.SwipeRefreshLayout> </LinearLayout> <ProgressBar android:id="@+id/loadMoreProgress" ... android:visibility="@{provider.recyclerViewState==RecyclerviewErrorStateType.LoadMore?View.VISIBLE:View.GONE}"/> </FrameLayout> </layout> Implementation now we just need this: 1. binding dividers is type of divider you want 2. layoutmanager is the manager for listing like linear grid and all 3. listners -> onRefreshListener,networkListener,onLoadMoreListener 5. Our item binding
<include layout="@layout/common_recyclerview_layout" android:layout_width="match_parent" android:layout_height="match_parent" app:provider="@{listingProvider}" app:bindingDivider="@{@string/rv_bottomDivider}" app:layoutManager="@{LayoutManagers.linear()}" app:onRefreshListener="@{onRefreshListener}" app:networkListener="@{networkListener}" app:onLoadMoreListener="@{onLoadMoreListener}"/> To test this I have created different states:
userListingBinding = new ListBindings.UserListingBinding(); userListingBinding.setRecyclerViewState(RecyclerviewErrorStateType.NetworkError); userListingBinding.setLoadMoreEnabled(true); activityMainBinding.setListingProvider(userListingBinding); @Override public void onTryAgainClick() { Toast.makeText(this,"onTryAgainClick",Toast.LENGTH_SHORT).show(); //dummy loadings loadData(); userListingBinding.setRecyclerViewState(RecyclerviewErrorStateType.LoadingStarted); } 