9
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

and factoryAdvent Calendar 2018

Day 11

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

Posted at

前回の内容

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

今回の内容

第2回はチャット部屋一覧のPub/Subを行なっていきたいと思います。
sample.gif

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

では早速実装をば!

部屋一覧のPub

やることは"rooms"という名前のCollectionを作って、そこにDocumentを追加するだけです!簡単ですね!

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を取得します。

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

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


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で登場するクラスです。

要所だけ説明すると、query.addSnapshotListener(this)で"rooms"テーブルをSubします。
"rooms"テーブルに変更があるとonEventが呼ばれます。引数のdocumentSnapshotsは追加/変更/削除されたDocumentのスナップショットです。
documentSnapshots.documentChangesで追加/変更/削除されたDocumentChangeのリストを取得します。DocumentChangeはDocumentの変更情報を持ったクラスです。変更内容によってAdapterへの処理を変えています。

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

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クラスのオブジェクトに変換しています。
部屋が選択されたら、RoomオブジェクトとDocumentIdをクロージャに渡します。

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

おわりに

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

9
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?