Reputation: 79
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
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
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
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
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
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