#はじめに
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になります。
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は投げれません
※IN句が最近追加されOR検索は可能
FirestoreにINQueryが対応されたのでSDK,Ballcapで試してみる
仮にANDのQueryが投げられたとしても
Cが作ったGroupにA,Bが参加している場合GroupもHitしてしまうためAB個別のRoomを取得するのは厳しそう
解決策
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の負荷分散とか
明日はShohei NakagawaさんがFlutterのFirebase Firestoreモデルライブラリを作った話(Flamingo)というタイトルで記事を投稿してくれるようです!
iosだといくつかライブラリがあるのですがFlutterのは聞いたことがなかったので楽しみです!
最後に
MessageStoreではSampleプロジェクトが用意されているのでまずは手元で動作確認してみたい方はこちらを参考に試してみてください!
MessageStoreでチャットの動作を確認
MessageStoreにはSampleプロジェクトが用意されているのでこちらで動作を確認してみましょう。
・MessageStoreをcloneしてみてください
・clone後projectのルートでpod install
・chat用にfirebaseプロジェクトを作成
・firebaseコンソールでauthenticationの匿名を有効にする
・firebaseコンソールでfirestoreを使えるようにする
動作チェックなのでテストモードで確認
・複合indexの設定
アプリを実行して複合Indexを貼るためのURLを得るものありです。
アプリを実行してlogより以下の文字をコピぺし検索すると複合index貼るようのurlが確認できると思います。
failed: The query requires an index