firebase realtime database 練習用に。
ブラウザとiOSでチャットの実装。
QRコードを介して招待したりされたり。
#成果物
デモ
https://anywaychat.netlify.com/
ブラウザ
https://github.com/nakadoribooks/anywaychat-web/releases/tag/v1.0.1
iOS
https://github.com/nakadoribooks/anywaychat-ios/releases/tag/v1.0.0
実装
database設計
よくわかってないが、浅く作るということで。
ブラウザ
準備
ライブラリ読み込み
index.html
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.5.0/css/bulma.min.css">
<script src="https://unpkg.com/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.24.0/babel.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/2.0.2/anime.js"></script>
<script src="https://www.gstatic.com/firebasejs/4.2.0/firebase.js"></script>
<script src="https://www.gstatic.com/firebasejs/3.8.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/3.8.0/firebase-database.js"></script>
サーバとの時間差を取ってくる
script.js
// 初期化
created: function () {
// firebaseの設定
this.setupFirebase().then(()=>{
// chatの設定
this.setupChat()
// メッセージを読み込む
this.loadMessage()
})
},
setupFirebase: function(){
// 略
return new Promise((resolve, reject)=>{
firebase.database().ref("/.info/serverTimeOffset").on('value', (offset) => {
var offsetVal = offset.val() || 0;
this.timestampOffset = offsetVal
resolve()
});
})
},
チャット(ルーム?)のセットアップ
ハッシュからchatIdを探す、あったらそれ使ってなかったらつくる
script.js
setupChat: function(){
let ref = firebase.database().ref('chats')
let hash = location.hash
// chatId があったとき
if(hash != null && hash.length > 0){
let chatId = hash.slice( 1 ) ;
this.chatRef = ref.child(chatId)
console.log("read chat", chatId)
}
// なかったとき
else{
let createdAt = this.timestamp()
// 新しいチャットを作って
this.chatRef = ref.push()
// 保存する
this.chatRef.set({
createdAt: createdAt
, createdAtReverse: -createdAt
})
location.href = location.origin + location.pathname + "#" + this.chatRef.key
}
this.qrUrl = "http://chart.apis.google.com/chart?chs=150x150&cht=qr&chl=anywaychat://" + this.chatRef.key
},
既存メッセージの取得
script.js
// メッセージを読み込む
, loadMessage:function(){
// 最初に全部取ってくる、
let messageListRef = this.chatRef.child("messageList")
// 1. messageIdのリスト(messageList)を取得
messageListRef.once('value').then((snapshot) => {
// 2. message の実態を持ってくる
var promiseList = []
let messageRef = this.messageRef()
snapshot.forEach(function(childSnapshot) {
var messageKey = childSnapshot.key
promiseList.push(messageRef.child(messageKey).once("value"))
})
return Promise.all(promiseList)
}).then((results) => {
// 3. 整形
// 略
// 5. 表示に反映
this.messageList = messageList
// 6. 下までスクロール
this.scrollToBottom()
// 7. 追加の監視
this.observeMessage()
});
},
メッセージ追加の監視
script.js
// メッセージの監視
observeMessage: function(){
let messageListRef = this.chatRef.child("messageList")
messageListRef.on("child_added", (snapshot) => {
let newMessageKey = snapshot.key
// すでにあるやつは弾く
for(var i=0,max=this.messageList.length;i<max;i++) {
let message = this.messageList[i]
if(message.key == newMessageKey){
return
}
}
// 実態を取りに行く
this.messageRef().child(newMessageKey).once("value").then((snapshot)=>{
// 整形して表示に反映
let data = snapshot.val()
data.key = snapshot.key
this.messageList.push(data)
this.scrollToBottom()
})
})
},
メッセージの送信
script.js
// メッセージを送る
send: function(event){
// 新しいメッセージを作って
let message = this.messageRef().push()
let createdAt = firebase.database.ServerValue.TIMESTAMP
// 保存する
let chat = this.chatRef.key
message.set({
chat: chat
, message:this.message
, userName: this.userName
, userId: this.userId
, createdAt: createdAt
, platform: PlatformType.browser.val
})
this.chatRef.child("messageList").child(message.key).set(1)
// 入力エリアリセット
this.message = ""
}
iOS
準備
Firebaseライブラリの登録
Podfile
target 'anywaychat' do
use_frameworks!
pod 'Firebase/Core'
pod 'Firebase/Database'
end
URLスキーム(anywaychat://)のハンドリング
招待(QRコード)からの起動ように。
AppDelegate.swift
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
if let host = url.host{
Global.currentChatId = host
}
return true
}
サーバとの時間差を取ってくる
ViewController.swift
private func setup(){
// 略
let offsetRef = Database.database().reference(withPath: ".info/serverTimeOffset")
offsetRef.observe(.value, with: { snapshot in
if let offset = snapshot.value as? TimeInterval {
Global.timestampOffset = offset
}
self.loadMessageList()
})
}
チャット(ルーム?)のセットアップ
あったらそれ使ってなかったらつくる
ViewController.swift
private func setupRef(){
let ref = Database.database().reference()
if let chatId = Global.currentChatId{
self.chatRef = ref.child("chats").child(chatId)
}else{
self.chatRef = ref.child("chats").childByAutoId()
Global.currentChatId = self.chatRef.key
}
}
既存メッセージの取得
複数の実態の取得の完了はどうやってとるのだろうか。
loaded ++ がなんともダサい.
ViewController.swift
private func loadMessageList(){
// 最初の読み込み
// messageIdList の取得
self.chatRef.child("messageList").observeSingleEvent(of: .value, with: { (snapshot) in
var results:[Message] = []
var loaded:UInt = 0
let total = snapshot.childrenCount
if total == 0{
self.onLoadMessageList()
return
}
// id を元にそれぞれの実態を取得
for child in snapshot.children{
guard let childSnapshot = child as? DataSnapshot else{
continue;
}
let messageKey = childSnapshot.key
self.messageRef().child(messageKey).observeSingleEvent(of: .value, with: { (snapshot) in
let message = Message(snapshot: snapshot)
results.append(message)
loaded = loaded + 1
if loaded == total{
results.sort(by: { (m1, m2) -> Bool in
return m1.createdAt.compare(m2.createdAt) == .orderedAscending
})
self.messageList = results
self.onLoadMessageList()
}
})
}
})
}
メッセージ追加の監視
ViewController.swift
private func onLoadMessageList(){
// 監視
self.chatRef.child("messageList").observe(.childAdded, with: { (snapshot) in
let newMessageKey = snapshot.key
// すでにあるやつは弾く
for m in self.messageList{
if m.key == newMessageKey{
return
}
}
// 実態を取ってくる
self.messageRef().child(newMessageKey).observeSingleEvent(of: .value, with: { (snapshot) in
// 表示に反映
let message = Message(snapshot: snapshot)
self.messageList.append(message)
let indexPath = IndexPath(row: self.messageList.count - 1, section: 0)
self.tableView.insertRows(at: [indexPath], with: .automatic)
})
})
}
メッセージの送信
ViewController.swift
func onCommitMessage(message: String) {
let createdAt = ServerValue.timestamp()
let messageDic:[String:Any] = [
"userId": Global.userId,
"createdAt": createdAt,
"chat": self.chatRef.key,
"message": message,
"userName": userName,
"platform": PlatformType.ios.val()
]
let ref = Database.database().reference()
let message = ref.child("messages").childByAutoId()
message.setValue(messageDic)
chatRef.child("messageList").child(message.key).setValue(1)
}
まとめ
ほぼ同じノリでかける。
次
Android