
通常、アプリではチャットなどをする際に、ユーザーAからユーザーBにメッセージを送ったら、リアルタイムで、ユーザーBにメッセージや通知がいくようにする必要があります。
Lineでもそうで、ユーザーAがメッセージをユーザーBに送ると、ユーザーBには画面上で更新されます。
これをFirestoreで実現してみましょう!
今回は2つの画面で実際にメッセージができないので、
Firestoreの値を変えた瞬間に、アプリの画面に自動反映されるかどうかの検証をしてみます。
そうすることで、ユーザーAがメッセージを送信して、それがFirestoreに書き込まれます。そしてその書き込まれた内容を、ユーザーBのアプリ画面に表示するといったイメージです。
FirestoreはFirebaseのソリューションで、データをNoSQLで貯めることのできるデータベースです。
通常HTTP通信では、WebサーバにリクエストをすることでWebサーバがレスポンスをしてくれますが、Webサーバが自分からクライアントサイドへリクエストをすることはありません。
リクエストをしたクライアントサイドにWebサーバはレスポンスをします。別のクライアントサイドへレスポンスやリクエストをしたりしません。
これを双方向通信と呼びます。
上の図のやり取りだと、ユーザーAがサーバへリクエストしてFirestoreに書き込みをします。そして普通であればFirestoreがユーザーAにレスポンスを返さないといけないですが、別のユーザーBにFirestoreが自ら情報を送らないといけません。
つまり、一方的なので片方向通信と呼びます。
このようにチャットアプリとなると、サーバが別のクライアントサイドやアプリに情報を自ら送る必要がありますが、どのようにすることで送れるのでしょうか?
それは、イベントリスナーを登録して、Firestoreのある部分のデータが更新や追加、削除されたら、その情報を該当者に送るという仕組みです。
イベントリスナーに登録して、更新/追加/削除というトリガーによってサーバが処理を始めるということです。
この仕組みを使って、
「ユーザーAがメッセージを送ってFirestore内にその情報が書き込まれたりしたら、それをトリガーとして、FirestoreがユーザーBに情報を送る」という仕組みが実現できるわけです。
この仕組みをFirestoreは実装されているので、
今回はこの仕組みを用いてアプリを実装してみたいと思います。
firestoreを用いた画面更新処理
▼ View
import SwiftUI
import Firebase
import FirebaseCore
struct TestView:View{
@ObservedObject public var obj:FirebaseOperatorViewModel = FirebaseOperatorViewModel()
var body: some View{
// firestoreからの表示
Text("\(obj.person.name)")
Text("\(obj.person.pref)")
Text("\(obj.person.age)")
// firestoreへの値更新
TextField("名前を入力してください", text: self.$obj.person.name)
TextField("都道府県を入力してください", text: self.$obj.person.pref)
TextField("年齢を入力してください", text: self.$obj.person.age)
// buttonでfirestore更新
Button(action: {
self.obj.updateDocument()
}, label: {
Text("Firestore更新")
})
}
}
▼ Model
// modelはstruct型にする。classはダメ
struct Person{
public var name:String = ""
public var pref:String = ""
public var age:String = ""
}
▼ ViewModel
/**
ViewModelでmodelの値を更新するため、firestoreから取得したデータをmodelに書き込んでViewを更新する。
*/
class FirebaseOperatorViewModel: ObservableObject{
@Published public var person:Person = Person()
public let db = Firestore.firestore()
public var hairdresser_id:String = "menu"
init(){
self.realtime_firestore_get()
}
/**
Firestoreのイベントリスナー設定
*/
func realtime_firestore_get()->Void{
self.db.collection("hairdresser_menu").addSnapshotListener { (documentSnapshot, error) in
// ドキュメントスナップショットの取得に失敗した場合はエラー内容を表示
guard let documentSnapshot = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
// 対象ドキュメントの変更内容
documentSnapshot.documentChanges.forEach{ diff in
self.db.collection("hairdresser_menu").document("\(self.hairdresser_id)").getDocument { (snap, error) in
if let error = error {
fatalError("\(error)")
}
guard let data = snap?.data() else { return }
self.person.name = data["name"]! as! String
self.person.pref = data["pref"]! as! String
self.person.age = data["age"]! as! String
}
}
print("リスナーをアタッチして、コールバックを受け取った。")
}
}
/**
値が更新されたらFirestoreへ書き込み更新
*/
func updateDocument()->Void{
let userRef = db.collection("hairdresser_menu").document("\(self.hairdresser_id)")
userRef.setData([
"name": self.person.name,
"pref": self.person.pref,
"age": self.person.age
])
}
}
こう書くことで、更新される。
realtime_firestore_get()はFirestoreが更新されたら自動的に変更検知して、更新データを取得するプログラムになりますが、
最初は変更があるかわからないため、最初はgetしてこないといけないと思ったが、
realtime_firestore_get()のイベントリスナーを読み込むだけで、通常のgetでのデータ取得しつつも、そのあとは常に待機中になって、firestoreの変更情報をずっと検知する。
挙動
realtime_firestore_get関数を実行しておくことで、イベントリスナー登録され、常に待機状態になります。
このrealtime_firestore_get関数内で指定した、コレクションとドキュメント配下にあるデータのどれかに変更や追加、削除が起きたとき、それを検知して、
realtime_firestore_get関数の中の処理が走ります。
こういったデータの更新などが走るたびに、realtime_firestore_get関数の処理が走るので、その都度、更新された値を取得して、Modelのプロパティに書き込むことで、ViewModelを通じて画面の更新が走るわけです。
Lineのように、アプリが待機状態でメッセージ画面を開きっぱなしでも、自動的にメッセージが送られると画面の更新が走り、一番上に最新メッセージが表示されます。
上記のイベントリスナーを用いることで、このような処理の構築が可能になります。

