【第2回】何人が何十回やってるか分からないが、それでも俺はFirebaseでチャットアプリを作りたい

# 前回の内容
[【第１回】何人が何十回やってるか分からないが、それでも俺はFirebaseでチャットアプリを作りたい](https://qiita.com/marumarumaru/items/63268025994946d63f61)

# 今回の内容
第2回はチャット部屋一覧のPub/Subを行なっていきたいと思います。
![sample.gif](https://qiita-image-store.s3.amazonaws.com/0/277406/b7d05257-2789-e250-1500-a853f2be692a.gif)

出来上がりはこんな感じですね！左がPubして、右がSubしてます！

では早速実装をば！

# 部屋一覧のPub
やることは"rooms"という名前のCollectionを作って、そこにDocumentを追加するだけです！簡単ですね！

```kotlin
val name = editText.editable.toString()
val createTime = TimeStamp(Date())
val room = Room(name, createTime)
db.collection("rooms")
        .add(room)
```
db.collenction("rooms")でroomsという名前のCollectionへの参照を取得して、ユーザーが入力した部屋名とTimeStampをRoomという名前のdataクラスに入れて、addしています。
TimeStampはDocumentのtimestampタイプと対応しています。
Firebaseコンソールにて、Firestore上で"rooms"Collectionにドキュメントが追加され、そのドキュメントの"name"フィールドに「Hello!」、"createTime"フィールドに日付と時刻が格納されていることが分かると思います。

部屋一覧のPubは以上です！

# 部屋一覧のSub

以下でQueryを取得します。

```kotlin
val query = db.collection("rooms")
                .orderBy("createTime")
```

db.collection("rooms")で"rooms"Collectionへの参照を取得し、orderBy("createTime")でcreateTimeの昇順でソートしています。

```kotlin

abstract class FireStoreAdapter<VH : RecyclerView.ViewHolder>(var query: Query)
    : RecyclerView.Adapter<VH>(), EventListener<QuerySnapshot> {

    private var registration: ListenerRegistration? = null

    private val snapshots: ArrayList<DocumentSnapshot> = ArrayList()

    fun startListening() {
        if (registration == null) {
            registration = query.addSnapshotListener(this)
        }
    }

    fun stopListening() {
        registration?.let {
            it.remove()
            registration = null
        }

        snapshots.clear()
        notifyDataSetChanged()
    }

    fun changeQuery(query: Query) {
        stopListening()

        snapshots.clear()
        notifyDataSetChanged()

        this.query = query
        startListening()
    }

    override fun getItemCount() = snapshots.size

    protected fun getSnapshot(index: Int): DocumentSnapshot {
        return snapshots[index]
    }

    override fun onEvent(documentSnapshots: QuerySnapshot?, e: FirebaseFirestoreException?) {
        if (e != null || documentSnapshots == null) {
            Log.w("error", "onEvent:error", e)
            return
        }

        for (change in documentSnapshots.documentChanges) {
            val snapShot = change.document

            when (change.type) {
                DocumentChange.Type.ADDED -> onDocumentAdded(change)
                DocumentChange.Type.MODIFIED -> onDocumentModified(change)
                DocumentChange.Type.REMOVED -> onDocumentRemoved(change)
            }
        }
    }

    protected fun onDocumentAdded(change: DocumentChange) {
        snapshots.add(snapshots.size, change.document)
        notifyItemInserted(snapshots.size)
    }

    protected fun onDocumentModified(change: DocumentChange) {
        if (change.oldIndex == change.newIndex) {
            snapshots.set(change.oldIndex, change.document)
            notifyItemInserted(change.oldIndex)
        } else {
            snapshots.removeAt(change.oldIndex)
            snapshots.add(change.newIndex, change.document)
            notifyItemMoved(change.oldIndex, change.newIndex)
        }
    }

    protected fun onDocumentRemoved(change: DocumentChange) {
        snapshots.removeAt(change.oldIndex)
        notifyItemRemoved(change.oldIndex)
    }
}
```

見たことある人もいるかもしれませんが、これは[Cloud Firestore Android Codelab](https://codelabs.developers.google.com/codelabs/firestore-android/#0)で登場するクラスです。<br>
要所だけ説明すると、query.addSnapshotListener(this)で"rooms"テーブルをSubします。
"rooms"テーブルに変更があるとonEventが呼ばれます。引数のdocumentSnapshotsは追加/変更/削除されたDocumentのスナップショットです。<br>documentSnapshots.documentChangesで追加/変更/削除されたDocumentChangeのリストを取得します。DocumentChangeはDocumentの変更情報を持ったクラスです。変更内容によってAdapterへの処理を変えています。

FireStoreAdapterを継承したAdapterを作ります。

```kotlin
class Adapter(query: Query,
              private val onClickItem: (room: Room?, documentId: String) -> Unit) : FireStoreAdapter<Adapter.ViewHolder>(query) {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    return ViewHolder(DataBindingUtil.inflate(
            LayoutInflater.from(parent.context),
            R.layout.item_room,
            parent,
            false)
    )
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(getSnapshot(position))
    }

    inner class ViewHolder(val binding: ItemRoomBinding)
        : RecyclerView.ViewHolder(binding.root) {

        fun bind(snapShot: DocumentSnapshot) {
            val room = snapShot.toObject(Room::class.java)
            binding.room.text = room?.name
            binding.room.setOnClickListener {
                onClickItem(room, snapShot.id)
            }
        }
    }
}
```

注目するところはViewHolder#bind()です。snapShot.toObject(Room::class.java)でRoomクラスのオブジェクトに変換しています。<br>部屋が選択されたら、RoomオブジェクトとDocumentIdをクロージャに渡します。

これで部屋一覧の実装は終わりです！

# おわりに
第1回ではログイン機能を作ってユーザー情報をFirestoreに追加していき、今回で部屋一覧の監視と追加を実装しました。次回はチャットアプリの肝であるチャット機能を実装します。