John L. Carveth
John L. Carveth

Reputation: 79

Empty View on a RecyclerView

In an app I am working on, I am using a Recycler View to show a list of items. I would like the list to display some view (For example, a text view) when the list is empty. I know that with ListViews, one could call setEmptyView, but RecyclerView has no such method.

I have tried setting the visibility of a view to GONE, and making it visible when the RecyclerView dataset is empty, but adding any view to the layout file where the RecyclerView is defined leads to an error:

Attempt to invoke virtual method 'boolean 
android.support.v7.widget.RecyclerView$ViewHolder.shouldIgnore()' on a null 
object reference

What is the best way to go about what I am doing?

For more info, the RecyclerView is held inside a Fragment, and the layout is inflated in the onCreateView function.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_grocerylist, container, false);

    rv = view.findViewById(R.id.grocery_list);

    dh = new DatabaseHandler(view.getContext());

    dataset = dh.getGroceries();

    // Set the adapter
    if (view instanceof RecyclerView) {
        Context context = view.getContext();
        RecyclerView recyclerView = (RecyclerView) view;
        recyclerView.setLayoutManager(new LinearLayoutManager(context));

        adapter = new GroceryListRecyclerViewAdapter(dataset,mListener,this);
        recyclerView.setAdapter(adapter);
    }

    ItemTouchHelper.Callback callback = new SimpleTouchHelperCallback(adapter);
    ith = new ItemTouchHelper(callback);
    ith.attachToRecyclerView(rv);

    DividerItemDecoration div = new DividerItemDecoration(rv.getContext(),
            DividerItemDecoration.VERTICAL);

    rv.addItemDecoration(div);

    return view;
}

The XML:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView 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:id="@+id/grocery_list" android:name="com.github.jlcarveth.grocer.layout.fragment.GroceryListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="0dp"
android:layout_marginRight="0dp"
app:layoutManager="LinearLayoutManager"  tools:context="com.github.jlcarveth.grocer.layout.fragment.GroceryListFragment"   tools:listitem="@layout/grocery_item"/>

Upvotes: 3

Views: 13017

Answers (5)

Rakesh Verma
Rakesh Verma

Reputation: 806

This answer is for developers using Kotlin and databinding in their Android project.

custom_recyclerview_empty_support.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:tools="http://schemas.android.com/tools">

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Recycler view -->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <!-- Empty view -->
    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/empty_text_view"
        style="@style/TextAppearance.MaterialComponents.Body1"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@color/white_light"
        android:gravity="center"
        android:textColor="@color/black_light"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="Empty View" />

    <!-- Retry view -->
    <androidx.appcompat.widget.LinearLayoutCompat
        android:id="@+id/retry_linear_layout_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@color/white_light"
        android:gravity="center"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/retry_text_view"
            style="@style/TextAppearance.MaterialComponents.Body1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="@dimen/views_pad_horizontal"
            android:layout_marginTop="@dimen/views_pad_vertical"
            android:layout_marginEnd="@dimen/views_pad_horizontal"
            android:layout_marginBottom="@dimen/views_pad_vertical"
            android:gravity="center"
            tools:text="Retry View" />

        <com.google.android.material.button.MaterialButton
            android:id="@+id/retry_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/retry_button" />
    </androidx.appcompat.widget.LinearLayoutCompat>

    <!--  Loading view -->
    <FrameLayout
        android:id="@+id/loading_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@color/white_light"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <ProgressBar
            android:id="@+id/progress_bar"
            style="?android:attr/progressBarStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />
    </FrameLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

The fragment class where I am using this Custom RecyclerView class

     <app.ui.widgets.RecyclerViewEmptySupport
            android:id="@+id/my_recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

Creating a Custom RecyclerView class :

class RecyclerViewEmptySupport(context: Context, attrs: AttributeSet) :
ConstraintLayout(context, attrs) {

private val binding: CustomRecyclerviewEmptySupportBinding
val recyclerView: RecyclerView

init {
    this.binding =
        CustomRecyclerviewEmptySupportBinding.inflate(LayoutInflater.from(context), this, true)
    this.recyclerView = binding.recyclerView
}

fun updateViews(
    loading: Boolean = false,
    retry: Boolean = false,
    empty: Boolean = false,
    success: Boolean = false
) {
    binding.recyclerView.setVisibleOrGone(success)
    binding.emptyTextView.setVisibleOrGone(empty)
    binding.retryLinearLayoutView.setVisibleOrGone(retry)
    binding.loadingView.setVisibleOrGone(loading)
}

// Empty view
fun showEmptyMessage(message: String) {
    updateViews(empty = true)
    binding.emptyTextView.text = message
}

// Retry view
fun showRetryView(message: String, callback: () -> Unit) {
    updateViews(retry = true)

    binding.retryTextView.text = message
    binding.retryButton.setOnClickListener { callback() }
}
}

The Fragment/Activity class can manage the Views visibility and setting data to views , like this :

When api is to be called , call this block of code ,

 binding.myRecyclerView.updateViews(loading = true)
 getDataFromRepository()

When data is empty in api response , call this,

binding.myRecyclerView.showEmptyMessage("No Items Message")

When data exists in api , call this ,

 list.addAll(apiList)
 adapter.notifyDataSetChanged()

 binding.myRecyclerView.updateViews(success = true)

When there is some error in api response , call this

 binding.myRecyclerView.showRetryView("Unable to get data at the moment.") { getDataFromRepository() }

Upvotes: 1

Adizbek Ergashev
Adizbek Ergashev

Reputation: 769

When I faced this issue, while surfing the web I found useful gist https://gist.github.com/sheharyarn/5602930ad84fa64c30a29ab18eb69c6e.

Here shown how to implement empty view in elegant way by defining data observer.

Upvotes: 0

Shambhu
Shambhu

Reputation: 191

Why don't you go for a Relative layout.

<Relative layout>
    <Recycler View/>

    <Text View/>
</Relative layout

As the array is empty

  RecyclerView.GONE
   TextView.VISIBLE

and if the array is having some data

  RecyclerView.VISIBLE
  TextView.GONE

In my case, I am following this only.

If doesn't work, I'll send you my code.

Upvotes: 0

diegoveloper
diegoveloper

Reputation: 103351

I updated your code:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:name="com.github.jlcarveth.grocer.layout.fragment.GroceryListFragment"
android:layout_width="match_parent"
tools:context="com.github.jlcarveth.grocer.layout.fragment.GroceryListFragment"
android:layout_height="match_parent">

<android.support.v7.widget.RecyclerView
    android:id="@+id/grocery_list"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="0dp"
    android:layout_marginRight="0dp"
    app:layoutManager="LinearLayoutManager"
    tools:listitem="@layout/grocery_item" />

<TextView
    android:id="@+id/empty_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:visibility="gone"
    android:text="NO DATA AVAILABLE" />
</FrameLayout>

Your Fragment:

 @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {


  View view = inflater.inflate(R.layout.fragment_grocerylist, container, false);

    rv = view.findViewById(R.id.grocery_list);
    TextView emptyView = (TextView)view.findViewById(R.id.empty_view);

    dh = new DatabaseHandler(view.getContext());

    dataset = dh.getGroceries();


     Context context = view.getContext();
     RecyclerView recyclerView = (RecyclerView) rv;
     recyclerView.setLayoutManager(new LinearLayoutManager(context));

     adapter = new GroceryListRecyclerViewAdapter(dataset,mListener,this);
     recyclerView.setAdapter(adapter);


    ItemTouchHelper.Callback callback = new SimpleTouchHelperCallback(adapter);
    ith = new ItemTouchHelper(callback);
    ith.attachToRecyclerView(recyclerView);

    DividerItemDecoration div = new DividerItemDecoration(recyclerView.getContext(),
            DividerItemDecoration.VERTICAL);

    recyclerView.addItemDecoration(div);


    if (dataset.isEmpty()){
        recyclerView.setVisibility(View.GONE);
         emptyView.setVisibility(View.VISIBLE);
    } else {
         recyclerView.setVisibility(View.VISIBLE);
         emptyView.setVisibility(View.GONE);
    }



    return view;
}

Upvotes: 3

TomD
TomD

Reputation: 614

Add a textview to your layout. At the same level as the RecyclerView

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

<TextView
android:id="@+id/empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone"
android:text="@string/no_data_available" />

At the onCreate or the appropriate callback check to see if the dataset that feeds your RecyclerView is empty. If it is then the RecyclerView is empty too. In that case, the message appears on the screen. If not, change its visibility:

private RecyclerView recyclerView;
private TextView emptyView;

// ...

recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
emptyView = (TextView) rootView.findViewById(R.id.empty_view);

// ...

if (dataset.isEmpty()) {
    recyclerView.setVisibility(View.GONE);
    emptyView.setVisibility(View.VISIBLE);
}
else {
    recyclerView.setVisibility(View.VISIBLE);
    emptyView.setVisibility(View.GONE);
}

Upvotes: 1

Related Questions