Help us understand the problem. What is going on with this article?

某SNSのチャットを真似る時に悩んだ話

はじめに

Firebase Advent Calendar 2019 6日目の記事になります

前日の5日目は@watanabe_yuさんが投稿されたFirebaseをなるべく安い料金で頑張りたい人へというタイトルの記事でした!

最近よくFirebaseって聞くけどぶっちゃけコスト面どんな感じなの?と気になる方もいらっしゃると思うので参考にしてみると良いですね!

前日の記事の紹介はここまでにして今回の記事は、
Firebaseを使用してチャット開発、その中でもルームの扱い方で悩んだのでその悩みと解決策を書いてみようと思います

開発中の悩みと解決策

メンバーが二人のルーム作成方法

チャットにはルームという概念が存在してますよね。
そしてルームは個別 or グループと大きく2つに分けられるのかなと思っております。

悩んだのは個別のルームの扱い方です。
Instagramのチャットルームをイメージしてもらえると良いかもしれません。

InstagramのStoryにアクションをした時の例です。

AがBの投稿したStoryXにいいねをした際にStoryXにAがいいねしたことをBにチャットで知らせます。
チャットが投稿されてるRoomをroomABとします。
このRoomABにはAとBのみがメンバーとして含まれます。

さらに、
AがBの投稿したStoryYにいいねをした場合にもStoryYにAがいいねしたとチャットで通知
BがAの投稿したStoryZにいいねした場合もStoryZにBがいいねしたとチャットで通知
これらのケースもRoomABに投稿されます。

Storyに対してRoomが作られるのではなくStoryを投稿した人とのRoomになるので個別Roomになります。

イメージ
スクリーンショット 2019-12-05 18.33.46.png

RoomのDocumentはこんな感じです。

struct Room: Modelable, Codable , RoomProtocol {

    typealias TranscriptType = Transcript

    var name: String?
    var thumbnailImage: File?
    var members: [String] = [] // Roomに入ってるUserIDが入る
    var lastTranscript: Transcript?
    var lastTranscriptReceivedAt: ServerTimestamp = .pending
    var isMessagingEnabled: Bool = true
    var isHidden: Bool = false
    var lastViewedTimestamps: [String : ServerTimestamp] = [:]
}

どうやれば
指定の二人のみが参加してる個別RoomのDocumentが引けるQueryが投げれるでしょうか?

membersにAとBが含まれるというANDのQueryでしょうか?
残念ながら型がArrayのFieldに対してANDのQueryは投げれません:cry:
※IN句が最近追加されOR検索は可能
FirestoreにINQueryが対応されたのでSDK,Ballcapで試してみる

仮にANDのQueryが投げられたとしても
Cが作ったGroupにA,Bが参加している場合GroupもHitしてしまうためAB個別のRoomを取得するのは厳しそう:disappointed_relieved:

解決策
RoomのDocumentにpairIDという二人のUserIDを足した文字列をFieldに設ける

Document<Room>
    .where("pairID", in: [user.id + ownerID, ownerID + user.id])
    .get() { (snapshots, error) in
        if let error = error {
            print(error)
            return
        }
        if (snapshots?.documents.count ?? 0) > 0 {
            // すでにルームができているのでいいね!の投稿のみ
        } else {
            // ルームがないので作成していいねの投稿
        }
}

Storyに対していいねというアクションが行われた場合、
いいねした人のIDとStoryを投稿したUserIDを足した文字列でRoomに対して検索を投げてます。

Queryの結果二人の個別Roomがまだない場合、
投稿したAのUserID + いいねしたBのIDをpairIDとしてroomのFiledに保存しそのroomを作成し、subCollectionのTranscriptsに"いいね"の内容を添えて保存します。
個別Roomが存在している場合は、
subCollectionのTranscriptsに"いいね"の内容を添えて保存するだけです。

[user.id + ownerID, ownerID + user.id]
二つ指定している理由は、もうお分かりの方もいらっしゃると思いますが、
投稿したBのUserID + いいねしたAのIDをpairIDとしてroomのFieldに保存される可能性があるためです。
AとB両方がStoryに投稿しどっちが先にいいねするかに起因してるためです。

正直一つは存在しないPairIDになるので無駄と言えば無駄ですが、これで現状解決してます。

他の解決案では、
userIDはユニークなので二つのIDを元に新しくユニークなIDを生成するという案もありましたが、実装とテストに時間がかかりそうだったので今回の方法を選択してます。

※追記

正直一つは存在しないPairIDになるので無駄と言えば無駄

この課題の解決策
コメントいただいた方法で
membersのArrayの中身がソートされている前提であれば以下Queryでも大丈夫そうです。
firestore.collection('rooms').where('members', '==', ['uidA', 'uidB'])

仕様次第なところがある内容なのですが、
userAが誰でも入れるグループを作りuserBがRoomに入り、その後にuserCがRoomに入る場合
['uidA', 'uidB'] <- userBが入る
['uidA', 'uidB', 'uidC'] <- userCが入る

タイミングによっては個別のRoomをQueryで引きたいのにGroupのRoomも引いて来てしまう可能性があるので、個別とGroupのRoomが混在する場合は個別のRoomを引けるように一つPairFiledを設けるのが良いと思っております。

pairIDを作成する方法についてはIDを比較してアルファベットの若い方を先にして文字列結合するなどの工夫をすればIN Queryを使わずとも個別Roomを引けそうな気がするのでINで指定する要素が一つ減ってラッキーって感じですかね!

締め

まだいくつかチャット系で知見あったんですがまたいつかの日に記事にしようと思います!
(Roomの負荷分散とか:frowning2:

明日はShohei NakagawaさんがFlutterのFirebase Firestoreモデルライブラリを作った話(Flamingo)というタイトルで記事を投稿してくれるようです!

iosだといくつかライブラリがあるのですがFlutterのは聞いたことがなかったので楽しみです!

最後に
MessageStoreではSampleプロジェクトが用意されているのでまずは手元で動作確認してみたい方はこちらを参考に試してみてください!

MessageStoreでチャットの動作を確認
MessageStoreにはSampleプロジェクトが用意されているのでこちらで動作を確認してみましょう。

MessageStoreをcloneしてみてください
・clone後projectのルートでpod install
・chat用にfirebaseプロジェクトを作成
・firebaseコンソールでauthenticationの匿名を有効にする
スクリーンショット 2019-12-05 17.09.43.png
・firebaseコンソールでfirestoreを使えるようにする
動作チェックなのでテストモードで確認
スクリーンショット 2019-12-05 17.12.11.png
・複合indexの設定

クエリにスコープはCollectionで大丈夫です
スクリーンショット 2019-12-05 20.04.19.png

アプリを実行して複合Indexを貼るためのURLを得るものありです。
アプリを実行してlogより以下の文字をコピぺし検索すると複合index貼るようのurlが確認できると思います。
failed: The query requires an index
スクリーンショット 2019-12-05 20.09.52.png

urlを元にwebページを開くと以下スクショの画面になるかと思うのでIndex作成ボタンを押してください
スクリーンショット 2019-12-05 16.18.34.png
・動作確認
ezgif.com-video-to-gif (1).gif

giiiita
giiiita a.k.a 溜池山王の技師(26歳) ゲームからマッチングアプリの技師へ
diverse
結婚支援事業を中心に、友達・恋人探しのマッチング事業を展開。深刻化する恋愛離れ、未婚率の上昇を解決すべくWEB・アプリサービスを展開。すべての人へ出会いのプラットフォームを提供しています。
http://diverse-inc.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした