RecyclerViewをAdapter.notifyDataSetChanged()を用いて更新できなかった理由を考察してみた

### 初投稿になります
　初めまして、これが初投稿になります。
　今年の四月にSEになり、そこからプログラミングを本格的に始めたばかりなのでまだまだ未熟ですが誰かの助けになる投稿ができればと思います。
　また詳しい方はいろいろ間違ってる部分の指摘等教えていただければ幸いです。

### 記事概要
　この記事では、``RecyclerView``の更新が``Adapter.notifyDataSetChanged()``でできない理由についての私的な考察のまとめです。同じお悩みの方の解決の一助になる、はず。
　私的というのは実際に``RecyclerView``クラスのコードから仕様を読み取ったわけではなく、実行結果から個人的な推測を行ったというニュアンスです。コードまで詳しい方は考察が正しいのか等教えていただけると助かります。
　また``RecyclerView``に関する実装方法など知っているが、``notifyDataSetChanged()``で更新できなかった人向けに書いているので、``RecyclerView``の実装方法にはあまり触れません。実装方法などわからない方は詳ししくは他の記事を調べてていただくといいと思います。公式は[こちら](https://developer.android.com/guide/topics/ui/layout/recyclerview)になります。
　また記載のコードは極力コピペで検証できるようにと思っておりますが、実行環境により動かない可能性もあります。ご了承ください。
　最後に、かなり長くなってしまったので考察に移る前に結論を書いて置きます。
　``Adapter.notifyDataSetChanged()``でできない理由は、``Adapter``が変更を認識するのは引数に入れられた変数自体ではなく、その変数の中身のリストだから、変数に別のリストを再代入すると変更が認識できないため。
　それではお時間ある方は最後まで考察にお付き合いいただければと思います。

### 考察内容
#### notifyDataSetChanged()が正常に動く場合
　そもそも更新がそれだけでできるのか？という話もあると思うので、先に``notifyDataSetChanged()``で更新できる例から紹介します。
  例としてボタンを押すとviewに数次が一つずつ増えていく簡単なアプリを作りました。

```xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:text="Button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="368dp"
        android:layout_height="439dp"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button" />
</android.support.constraint.ConstraintLayout>
```

```xml:list.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/textView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:text="TextView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
```

```kotlin:ListData.kt
data class ListData(var text : String)
```

```kotlin:ViewHolder.kt
class ViewHolder(view:View):RecyclerView.ViewHolder(view){
    val text :TextView = view.findViewById(R.id.textView)
}
```

```kotlin:ViewAdapter.kt
class ViewAdapter(val list :List<String>,val context :Context):RecyclerView.Adapter<ViewHolder>(){

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

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.text.text = list[position]
    }

    override fun getItemCount(): Int {
        return list.size
    }
}
```

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {
    var sampleList = mutableListOf<String>("0")
    var i = 1

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val Adapter = ViewAdapter(sampleList,this)
        val viewManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)

        recyclerView.apply {
            setHasFixedSize(true)
            layoutManager = viewManager
            adapter = Adapter
        }

        button.setOnClickListener{
            sampleList.add(i.toString())
            Adapter.notifyDataSetChanged()
            i++
        }
    }
}
```

これでボタンを押せば``RecyclerView``に数字が増えていくものができました。私の環境ではちゃんと更新されました。

#### 更新されない一例
　上記のコードで、``MainActivity.kt``を以下のように変えてしまうと更新されなくなってしまいます。

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {
    var sampleList = mutableListOf<String>("0")  //(1)
    var i = 1

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val Adapter = ViewAdapter(sampleList,this)  //(2)
        val viewManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)

        recyclerView.apply {
            setHasFixedSize(true)
            layoutManager = viewManager
            adapter = Adapter //(3)
        }

        button.setOnClickListener{
            sampleList = mutableListOf<String>() //(4)
            for (j in 0..i){
                sampleList.add(j.toString())
            }
            Adapter.notifyDataSetChanged() //(5)
            i++
        }
    }
}
```

こんなあほみたいな実装せんわ！！と突っ込みたいのはわかりますが、でも``notifyDataSetChanged()``で更新できない一例をミニマムに示そうとした結果です。一見私のような不束者には前例と同じように動きそうに見えるのですが、このようにしてしまうとボタンを押しても更新されません。

#### では何が問題なのか
　問題となっているのは``sampleList``に``mutableListOf()``を再代入している所です。
　それではなぜ問題なのか。それは``Adapter``にセットした``sampleList``の中身の``mutableList``と再度代入した``mutableList``が違うものだからっぽいです。上のコメントアウトをもとに順を追って説明します。
　
　(1)で代入されている``mutableList``をリストAとします。``sampleList``はただの箱で、その中身がリストAだという認識です。このリストAが(2)で``Adapter``にセットされ、その``Adapter``が(3)で``recyclerView``に適応されています。つまりリストAが``Adapter``、ひいては ``recyclerView``に反映されています。
　(4)で``sampleList``に``mutableList``を再代入していますが、これをリストBとします。このリストBに要素を加えているのが``for()``の部分になります。
　さて問題の(5)のところです。``sampleList``という箱の中身はリストBに変化していますが、``Adapter``にセットされているリストAには何の追加も変更もありません。よって``Adapter.notifyDataSetChanged()``を実行してもリストAの変化を``recyclerView``に反映するので、``View``になんの変化もないわけです。プレゼントを受け取った後、その箱に新しい中身が加えられようが知ったこっちゃないっていう話です。

　これ、言われてみれば当たり前じゃんって話なんだと思いますが、私は最近までわからずに、毎回``Adapter``にも再代入して、``recyclerview``にも再代入してっていうことをやっていました。

####はまりポイント
　この実装のはまりポイントは``Adapter``は引数にとった変数の変化をキャッチするのではないということです。``val``で基本的に書かれることを想定してのことだと思います。だからセオリー通りどうしてもというとき以外は``val``を使って実装する方はこの問題に直面しないのかもしれません。そのような多くの方々にはなぜ``var``で再代入なんてしようと思ったんだと思われるかと思います。
　今回の例で再代入しようとする人はいないかもしれませんが、リストの全部または部分的に入れ替える際には、すでに入れ替えて表示したいリストがある状態(ボタンを押すとあらかじめ用意していたリストと切り替えるとか)なら再代入したほうが行数も処理もわかりやすくなってやってしまうことがあるのではないでしょうか？
　...え、そんな奴いない？私だけ？それは失礼しました。

###結論と対策
　要するに``val``を基本的に使おうっていう方針を守りつつ、``.removeAll{}``とか``.add()``とか使ってリストを更新しようなってことですかね。

###まとめ
　初投稿最後までお読みいただきありがとうございました。読みずらく、単調に長いものになってしまったのではと反省してます。今後も何かあれば投稿いたしますので、どんどん役立つ投稿ができるように精進します。