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() }