Adv

Android RecyclerView States/Status

RecyclerView’s main purpose is to render lists of data. That data can be fetched from anywhere, including memory, hard disk, database and over network. There is no guarantee that we will find that data when we are attempting to fetch it since errors can occur along the way.

Moreover, fetching that data especially over a network involves some latency and wait time. In short, there are various states that working with data involves and it makes sense for our recyclerview to be able to automatically react to these states.

There are several open source examples and libraries that already solve this for us. In this thread we want to share some good examples and libraries that involve the recyclerview states or kotlin. Code will be in either Kotlin or Java.

Feel free to share some yourself.

Share

Related Concepts

Adv

1 Example

  1. Chameleon

    You can guess from the name that Chameleon deals with the Status of RecyclerView. It is a library written in Kotlin by extending the Constraint Layout and is androidx compatible. It does not utilize any external dependency and is lightweight.

    Step 1

    The first step is installation. Chameleon is hosted inĀ  jitpack. So first register jitpack in your project-level build.gradle:

     

    repositories {
        maven { url 'https://jitpack.io' }
    }

     

    Then in the app level build.gradle do install it by specifying the dependency and syncing:

     

    dependencies {
        implementation 'com.github.sangcomz:Chameleon:v0.2.0'
    }

     

    Step 2 – Layout

    Wrap your recyclerview using Chameleon like this:

    <xyz.sangcomz.chameleon.Chameleon 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/root"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:emptyButtonBackgroundColor="@color/colorPrimary"
        app:emptyButtonText="+Add Chameleon"
        app:emptyButtonTextColor="#ffffff"
        app:emptyButtonTextSize="12sp"
        app:emptyDrawable="@drawable/ic_empty"
        app:emptySubText="@string/sub_empty"
        app:emptyText="@string/empty"
        app:errorButtonBackgroundColor="@color/colorPrimary"
        app:errorButtonText="Retry"
        app:errorButtonTextColor="#ffffff"
        app:errorButtonTextSize="12sp"
        app:errorDrawable="@drawable/ic_error"
        app:errorSubText="@string/sub_error"
        app:errorText="@string/error"
        app:isLargeProgress="true"
        app:progressDrawable="@drawable/drawable_progress"
        app:useEmptyButton="true"
        app:useErrorButton="true"
        app:defaultState="LOADING"
        tools:context="xyz.sangcomz.chameleonsample.MainActivity">
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv_main_list"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </xyz.sangcomz.chameleon.Chameleon>

    Kotlin Code

    Then in code:

     

    // Set state using showState
    chameleon.showState(Chameleon.STATE.CONTENT)
    chameleon.showState(Chameleon.STATE.LOADING)
    chameleon.showState(Chameleon.STATE.EMPTY)
    chameleon.showState(Chameleon.STATE.ERROR)
    chameleon.showState(Chameleon.STATE.NONE)
    
    // Or use the extension functions
    chameleon.setContent()
    chameleon.setLoading()
    chameleon.setEmpty()
    chameleon.setError()
    chameleon.setNone()
    
    // As well as toggling between states with a boolean
    viewModel.isLoading.observe(this, Observer {
        chameleon.loadingOrContent(it) // true = LOADING, false = CONTENT
    })
    
    chameleon.contentOrEmpty(true) // CONTENT
    chameleon.contentOrEmpty(false) // EMPTY
    
    chameleon.setEmptyButtonClickListener { Toast.makeText(this, "Empty Button!", Toast.LENGTH_LONG).show() }
    chameleon.setErrorButtonClickListener { Toast.makeText(this, "Error Button!", Toast.LENGTH_LONG).show() }
    chameleon.setStateChangeListener { newState, oldState -> Log.d("Main", "Was $oldState is now $newState") }

     

    Full Example

    Here is a full example of recyclerview with different states using Chameleon library.

    (a). Chameleon.kt

    A simple data class for a single animal:

     

    data class Chameleon(val petName: String,
                         val drawableId: Int)

     

    ChameleonAdapter.kt

    Then create a simple recyclerview adapter to adapt the animals to recyclerview.

     

    import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import androidx.appcompat.widget.AppCompatImageView
    import androidx.appcompat.widget.AppCompatTextView
    import androidx.recyclerview.widget.RecyclerView
    import com.squareup.picasso.Picasso
    
    /**
     * Created by sangcomz on 27/03/2018.
     */
    class ChameleonAdapter : RecyclerView.Adapter<ChameleonAdapter.ChameleonViewHolder>() {
        private var chameleonList: List<Chameleon> = arrayListOf()
    
        fun setChameleonList(chameleonList: List<Chameleon>) {
            this.chameleonList = chameleonList
            notifyDataSetChanged()
        }
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChameleonViewHolder {
            val view: View = LayoutInflater.from(parent.context).inflate(R.layout.item_chamelon, parent, false)
            return ChameleonViewHolder(view)
        }
    
        override fun getItemCount(): Int = chameleonList.size
    
        override fun onBindViewHolder(holder: ChameleonViewHolder, position: Int) {
            holder.setItem(chameleonList[position])
        }
    
    
        inner class ChameleonViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    
            private val ivItemChameleon: AppCompatImageView = itemView.findViewById(R.id.iv_item_chameleon)
            private val tvItemChameleon: AppCompatTextView = itemView.findViewById(R.id.tv_item_chameleon)
    
            fun setItem(chameleon: Chameleon) {
                Picasso.get()
                    .load(chameleon.drawableId)
                    .fit()
                    .centerCrop()
                    .into(ivItemChameleon)
                tvItemChameleon.text = chameleon.petName
            }
    
        }
    }

     

     

    (b). Store.kt

    Hold the beautiful animals and their images for demo purposes.

     

    import io.reactivex.Flowable
    
    fun getChameleons(): Flowable<List<Chameleon>> {
        return Flowable.just(arrayListOf(
                Chameleon("Liam", R.drawable.chameleon1),
                Chameleon("Noah", R.drawable.chameleon2),
                Chameleon("Elijah", R.drawable.chameleon3),
                Chameleon("Logan", R.drawable.chameleon4),
                Chameleon("Mason", R.drawable.chameleon5),
                Chameleon("Emma", R.drawable.chameleon6),
                Chameleon("Olivia", R.drawable.chameleon7),
                Chameleon("Ava", R.drawable.chameleon8)
        ))
    }
    
    fun getChameleonList(): List<Chameleon> = arrayListOf(
            Chameleon("Liam", R.drawable.chameleon1),
            Chameleon("Noah", R.drawable.chameleon2),
            Chameleon("Elijah", R.drawable.chameleon3),
            Chameleon("Logan", R.drawable.chameleon4),
            Chameleon("Mason", R.drawable.chameleon5),
            Chameleon("Emma", R.drawable.chameleon6),
            Chameleon("Olivia", R.drawable.chameleon7),
            Chameleon("Ava", R.drawable.chameleon8)
    )

     

    MainActivity.kt

    Finally the wrap everything up in the main activity.

     

    import android.os.Bundle
    import androidx.core.content.ContextCompat
    import androidx.appcompat.app.AppCompatActivity
    import androidx.recyclerview.widget.DividerItemDecoration
    import androidx.recyclerview.widget.LinearLayoutManager
    import android.view.Menu
    import android.view.MenuItem
    import android.widget.Toast
    import io.reactivex.android.schedulers.AndroidSchedulers
    import kotlinx.android.synthetic.main.activity_main.*
    import xyz.sangcomz.chameleon.Chameleon
    import xyz.sangcomz.chameleon.model.ButtonSettingBundle
    import xyz.sangcomz.chameleon.model.TextSettingBundle
    import java.util.concurrent.TimeUnit
    
    class MainActivity : AppCompatActivity() {
    
        private val mChameleonAdapter: ChameleonAdapter by lazy { ChameleonAdapter() }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            root.apply {
                setEmptyButtonClickListener { Toast.makeText(this@MainActivity, "Empty Button!", Toast.LENGTH_LONG).show() }
                setErrorButtonClickListener { Toast.makeText(this@MainActivity, "Error Button!", Toast.LENGTH_LONG).show() }
                setStateChangeListener { newState, oldState ->
                    Toast.makeText(this@MainActivity, "state was $oldState and now is $newState", Toast.LENGTH_LONG).show()
                }
            }
            setChameleonList()
        }
    
        private fun setChameleonList() {
            rv_main_list.apply {
                adapter = mChameleonAdapter
                layoutManager = LinearLayoutManager(this@MainActivity)
                addItemDecoration(DividerItemDecoration(this@MainActivity, DividerItemDecoration.VERTICAL))
            }
            getChameleons()
                    .delay(5000, TimeUnit.MILLISECONDS)
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(
                            {
                                mChameleonAdapter.setChameleonList(it)
                                root.showState(Chameleon.STATE.CONTENT)
                            },
                            {
                                root.showState(Chameleon.STATE.ERROR)
                            })
    
        }
    
        override fun onCreateOptionsMenu(menu: Menu?): Boolean {
            menuInflater.inflate(R.menu.main_menu, menu)
            return super.onCreateOptionsMenu(menu)
        }
    
        override fun onOptionsItemSelected(item: MenuItem?): Boolean {
            when (item?.itemId) {
                R.id.menu_displayState -> {
                    Toast.makeText(this, "State is ${root.getState()}", Toast.LENGTH_LONG).show()
                }
                R.id.menu_content -> {
                    root.showState(Chameleon.STATE.CONTENT)
                }
                R.id.menu_loading -> {
                    root.showState(Chameleon.STATE.LOADING)
                }
                R.id.menu_empty -> {
                    root.showState(Chameleon.STATE.EMPTY,
                            ContextCompat.getDrawable(this, R.drawable.ic_chameleon_red))
                }
                R.id.menu_error -> {
                    root.showState(Chameleon.STATE.ERROR,
                            ContextCompat.getDrawable(this, R.drawable.ic_chameleon_blue),
                            TextSettingBundle("Error Bundle Title"),
                            TextSettingBundle("Error Bundle Content"),
                            ButtonSettingBundle("Error Bundle Button", listener = {
                                Toast.makeText(this, "Custom Action", Toast.LENGTH_SHORT).show()
                            }))
                }
            }
            return super.onOptionsItemSelected(item)
        }
    }

     

    Run

    Here is the demo of what you get:

    Android RecyclerView with Different States

     

    Download

    1. Direct Download the code here.
    2. Browse or Read more here.
    3. Follow the code author here.

     




Share an Example

Share an Example

Browse
What is the capital of Egypt? ( Cairo )