Android app development with Kotlin: RecyclerView

Kotlin: 1.2.71
Android Studio: 3.2.1

#RecyclerView
It's a kind of listview.

This is a sample made with RecyclerView
![RecyclerView Sample.jpg](https://qiita-image-store.s3.amazonaws.com/0/245077/77ec1d8f-4f86-8337-3ef3-40202f3c45c0.jpeg)
##What do you need to prepare?
・Adapter (This will hold the Items and View
・Listener (Callback Interface for watching list click event
・ItemsArray (Items to show
・Layout of cell to show (Cell's layout

##Create the base of RecyclerView
Android Studio has a feature which helps making base components easier. Right click the package you want to add, go to **New -> Fragment -> Fragment(List)**
<img width="512" alt="AddRoot.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/245077/4ae4e4d3-962c-e51d-7680-002c34d8fe15.jpeg">
Then, configure the RecyclerView
<img width="512" alt="ConfigureRecyclerView.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/245077/d311285b-b697-92fd-e203-10dbaa1643cf.jpeg">
Object Kind: Enter any name, but I don't use it
Object content layout file name: Layout for cell
List layout file name: RecyclerView layout
Adapter class name: Adapter's name. In this case, I prefer ItemAdapter or ItemFragmentAdapter.

##Fragment
When the RecyclerView is created in this way, Android Studio will creates Fragment like this

```kotlin:ItemFragment.kt
class ItemFragment : Fragment() {

    // TODO: Customize parameters
    private var columnCount = 1

    private var listener: OnListFragmentInteractionListener? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        arguments?.let {
            columnCount = it.getInt(ARG_COLUMN_COUNT)
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment_item_list2, container, false)

        // Set the adapter
        if (view is RecyclerView) {
            with(view) {
                layoutManager = when {
                    columnCount <= 1 -> LinearLayoutManager(context)
                    else -> GridLayoutManager(context, columnCount)
                }
                adapter = MyItemRecyclerViewAdapter(DummyContent.ITEMS, listener)
            }
        }
        return view
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        if (context is OnListFragmentInteractionListener) {
            listener = context
        } else {
            throw RuntimeException(context.toString() + " must implement OnListFragmentInteractionListener")
        }
    }

    override fun onDetach() {
        super.onDetach()
        listener = null
    }

    /**
     * This interface must be implemented by activities that contain this
     * fragment to allow an interaction in this fragment to be communicated
     * to the activity and potentially other fragments contained in that
     * activity.
     *
     *
     * See the Android Training lesson
     * [Communicating with Other Fragments](http://developer.android.com/training/basics/fragments/communicating.html)
     * for more information.
     */
    interface OnListFragmentInteractionListener {
        // TODO: Update argument type and name
        fun onListFragmentInteraction(item: DummyItem?)
    }

    companion object {

        // TODO: Customize parameter argument names
        const val ARG_COLUMN_COUNT = "column-count"

        // TODO: Customize parameter initialization
        @JvmStatic
        fun newInstance(columnCount: Int) =
                ItemFragment().apply {
                    arguments = Bundle().apply {
                        putInt(ARG_COLUMN_COUNT, columnCount)
                    }
                }
    }
}
```
In my case, I only use these

```kotlin:RecyclerViewSampleFragment.kt
class RecyclerViewSampleFragment : Fragment(), ItemClickListener {

    private lateinit var sampleAdapter: RecyclerViewSampleAdapter
    private var samples = ArrayList<FragmentType>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        samples.add(FragmentType.COUNTER_SAMPLE)
        samples.add(FragmentType.EDITOR_SAMPLE)
        samples.add(FragmentType.IMAGE_VIEW_SAMPLE)
        samples.add(FragmentType.BOTTOM_NAVIGATION_SAMPLE)
        samples.add(FragmentType.WEBVIEW_SAMPLE)
        sampleAdapter = RecyclerViewSampleAdapter(samples, this)
        setHasOptionsMenu(true)
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.recyclerview_layout, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        recycler_view.apply {
            this.layoutManager = LinearLayoutManager(context)
            this.adapter = sampleAdapter
        }
    }

    override fun onItemSelected(fragmentType: FragmentType) {
        (activity?.application as App).mainActivity?.addFragment(fragmentType)
    }

    companion object {
        // TODO: Customize parameter initialization
        @JvmStatic
        fun newInstance() = RecyclerViewSampleFragment()
    }
}
```
**onCreate()**: Making the items to show in RecyclerView and initializing adapter
**onCreateView()**: specify the layout including RecyclerView
**onViewCreated()**: Apply **layoutManager** and **adapter** to RecyclerView
**onItemSelected()**: Custom Interface of notifying list click event
##Adapter
Android Studio creates Adapter like this

```kotlin
class MyItemRecyclerViewAdapter(
        private val mValues: List<DummyItem>,
        private val mListener: OnListFragmentInteractionListener?)
    : RecyclerView.Adapter<MyItemRecyclerViewAdapter.ViewHolder>() {

    private val mOnClickListener: View.OnClickListener

    init {
        mOnClickListener = View.OnClickListener { v ->
            val item = v.tag as DummyItem
            // Notify the active callbacks interface (the activity, if the fragment is attached to
            // one) that an item has been selected.
            mListener?.onListFragmentInteraction(item)
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context)
                .inflate(R.layout.fragment_item2, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = mValues[position]
        holder.mIdView.text = item.id
        holder.mContentView.text = item.content

        with(holder.mView) {
            tag = item
            setOnClickListener(mOnClickListener)
        }
    }

    override fun getItemCount(): Int = mValues.size

    inner class ViewHolder(val mView: View) : RecyclerView.ViewHolder(mView) {
        val mIdView: TextView = mView.item_number
        val mContentView: TextView = mView.content

        override fun toString(): String {
            return super.toString() + " '" + mContentView.text + "'"
        }
    }
}
```
**init**: Add and do something on when adapter created.

**onCreateViewHolder()**: Specify the layout using as the cell

**onBindViewHolder()**: Set data to the view

**getItemCount()**: return the number of Items in array.

**ViewHolder Class**: Define the view to use (Which need to be defined on cell's layout

On my case:

```kotlin:
class RecyclerViewSampleAdapter(private val samples: ArrayList<FragmentType>, private val listener: ItemClickListener) : RecyclerView.Adapter<RecyclerViewSampleAdapter.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.recyclerview_item, parent, false))
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = samples[position]
        holder.sampleView.text = item.title
        holder.itemView.setOnClickListener { listener.onItemSelected(item) }
    }

    override fun getItemCount() = samples.size

    inner class ViewHolder(mView: View) : RecyclerView.ViewHolder(mView) {
        val sampleView: TextView = mView.sample_name
    }
}
```
If list itens are selected, **ItemClickListener** will notify the event and gives selected item.
##Cell Layout
Based on Android Studio created

```xml:recyclerview_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/sample_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/text_margin"
        android:textAppearance="?attr/textAppearanceListItem" />
</LinearLayout>
```
##RecyclerView Layout
Based on Android Studio created

```xml:recyclerview_layout.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/recycler_view"
    android:name="galaxysoftware.sampleapp.fragment.RecyclerViewSamplesFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="16dp"
    android:layout_marginRight="16dp"
    android:background="#FFFFFF"
    app:layoutManager="android.support.v7.widget.LinearLayoutManager"
    tools:context=".fragment.RecyclerViewSampleFragment"
    tools:listitem="@layout/recyclerview_item" />
```
##Ready
Now, it's ready to use RecyclerView
Also I'll recommend to remove **DummyContent** in **dummy** package which Android Studio created.

##What should you do when the list items are changed?
You need to notify adapter, what is changed.
Here are the methods to notify
<img width="546" alt="AdapterNotifyMethods.png" src="https://qiita-image-store.s3.amazonaws.com/0/245077/5e5bf5b9-7ec5-fb5f-b340-3b4cbc0ac6ed.png">
Example: If you want to refresh everything, then use **adapter.notifyDataSetChanged()** (On my case, sampleAdapter.notifyDataSetChanged()
##Use with Swipe Refresh
You might see many times that list is refreshed by Swiping down. Let's make this possible
First, edit the layout to this

```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/refresh_list"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:name="galaxysoftware.sampleapp.fragment.RecyclerViewSamplesFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:background="#FFFFFF"
        app:layoutManager="android.support.v7.widget.LinearLayoutManager"
        tools:context=".fragment.RecyclerViewSampleFragment"
        tools:listitem="@layout/recyclerview_item" />
</android.support.v7.widget.RecyclerView>
```
Then add this to fragment

```kotlin
refresh_list.setOnRefreshListener {
    //Refresh items
            
    sampleAdapter.notifyDataSetChanged()
    refresh_list.isRefreshing = false
}
```
Refresh items and call **notifyDataSetChanged()**, **refresh_list.isRefreshing = false**
**When to call notify and end refreshing is depends on how you refresh items.**
So the case if you are requesting JSON result from the API, you have to wait for the response before notifying and end refreshing to avoid unexpected behavior.
#Project:
Code: https://github.com/GalaxyDevGamer/Android-Samples/blob/master/app/src/main/java/galaxysoftware/androidsamples/fragment/RecyclerViewSampleFragment.kt
Layout: https://github.com/GalaxyDevGamer/Android-Samples/blob/master/app/src/main/res/layout/recyclerview_layout.xml