12
11

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.

FirebaseAdvent Calendar 2016

Day 14

Firebaseでグループチャットを作る時、所属する全てのグループの書き込みを一つのストリームで受け取る

Last updated at Posted at 2016-12-14

要件

一ユーザーが複数のグループに所属できるグループチャットが作りたいとします。
FirebaseのDatabaseは下記のようだとします。

database.json

{
  "Groups" : {
    "group0" : [ "comment0-0", "comment0-1", "comment0-2" ],
    "group1" : [ "comment1-0", "comment1-1", "comment1-2" ],
    "group2" : [ "comment2-0", "comment2-1", "comment2-2" ]
  },
  "Users" : {
    "user1" : [ "group0", "group1" ]
  }
}

user1の場合/User/user1 の配列にグループ名を追加すればそのグループに所属したことになるとします。

課題

iOSアプリを書く際に困る点として、所属するグループが動的に変化するためその変化に合わせてDatabaseのListen先を切り替えなければいけないことが挙げられます。
そこで所属先リストのObserverを引数に所属している全グループの書き込みをObserveするクラスを作りたいです。

  • 引数:belongGroupIDsObservable
    • ユーザーが所属しているグループのIDのリストのObserver
    • 型はObservable<[String]>
  • 戻り値:下記書き込み辞書のObserver
  • 書き込み辞書:allGroupsComments
    • ユーザーが所属しているグループの書き込みの辞書
    • 型は[String:[String]]
    • keyがGroupIDでvalueが書き込みのリスト
/*
 * このコードが動くようにしたい
 * /
 * 
GroupsCommentsListner(belongGroupIDsObservable).listen().subscribe{ allGroupsComments in
  print(allGroupsComments.element!)
 /*
  * [
  *   "group0": ["comment0-0", "comment0-1", "comment0-2"],
  *   "group1": ["comment1-0", "comment1-1", "comment1-2"]
  * ]
  * /
}

よくある要件だと思いますが、正直自分もこの方法がベストだと思えないので、対案募集がてら投稿します。

実装の概要

  1. 流れてきた所属先リストをfor文で回し、全てのグループにコネクションを貼る
    1. この際、Firebaseのライブラリからhandleというコネクションの識別子が渡されるのでそれを控えておく
  2. 書き込みが流れてきたらPublishSubjectに送信する
  3. RxSwiftのScan関数を使って2のPublishSubjectを全てのグループの書き込みの辞書に変換する

できたコード

呼び出し側

main.swift

        FIRApp.configure()
        let belongGroupIDsObservable = PublishSubject<[String]>()
        let ref = FIRDatabase.database().reference()
        ref.child("/Users/user1").observe(.value, with: { event in
            
            guard let value = event.value as? [String] else {
                return
            }
            belongGroupIDsObservable.onNext(value)
        })
        
        GroupsCommentsListener(belongGroupIDsObservable).listen().subscribe{ allGroupsComments in
            print(allGroupsComments.element!)
        }

listener.swift
class GroupsCommentsListener {
    
    // ユーザーが所属している全てのグループの全ての書き込み
    //[GroupID:[Comment]]
    var allGropusComments:Variable<[String:[String]]>
    // ユーザーの所属しているGroup
    //このvalueを変更したら自動的にFirebaseListenerをつなぎ直す事が目標
    var belongGroupIDs:Observable<[String]>
    // FirebaseのGroupをListenしたときに渡されるHandleを保存しておく
    var fbCommentsListenerHandles:[String:UInt] = [:]
    
    init(_ belongGroupIDs:Observable<[String]>) {
        allGropusComments = Variable([:])
        self.belongGroupIDs = belongGroupIDs
        subscribeBelongs()
    }
    
    func listen() -> Observable<[String:[String]]> {
        return allGropusComments.asObservable()
    }
    
    // ユーザーの所属グループの変化を監視する
    // 変化があったら全てのCommentsへの接続をクリアし、再度接続しなおす
    func subscribeBelongs() {
        belongGroupIDs.subscribe{
            self.removeListeners()
            guard let value = $0.element as? [String]? , let value2 = value else {
                return
            
            }
            self.setCommentsListener(currentBelongs: value2)
        }
    }
    
    
    // Groupへの書き込みをListenする
    // 書き込みイベントを一本のObserverにまとめる
    // この時scanメソッドを使う
    func setCommentsListener(currentBelongs:[String]) {
        let ps = PublishSubject<[String:[String]]>()
        
            let ref = FIRDatabase.database().reference()
        
            for belong in currentBelongs {
                let handle = ref.child("/Groups/\(belong)").observe(.value, with: { event in
                    ps.onNext([belong:event.value! as! [String]])
                })
                self.fbCommentsListenerHandles[belong] = handle
            }
        
        
        ps
            .scan([:], accumulator: { acum, elem in
                var newDic = acum
                let key = elem.keys.first!
                let value = elem[key]!
                newDic[key] = value
                return newDic
            })
            .subscribe{self.allGropusComments.value = $0.element! as! [String:[String]]}
    }
    
    // Firebaseの監視の解除
    func removeListeners(){
        let ref = FIRDatabase.database().reference()
        for (groupID,handle) in fbCommentsListenerHandles {
            ref.child("Groups/\(groupID)").removeObserver(withHandle: handle)
        }
        fbCommentsListenerHandles = [:]
    }
}

とりあえずうごきました。
もう少し良い実装方法を思いついた方Wanted!

12
11
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
12
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?