25
27

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.

firebaseでiOSとブラウザのチャット

Last updated at Posted at 2017-08-31

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設計

よくわかってないが、浅く作るということで。

スクリーンショット 2017-08-31 16.23.55.png

ブラウザ

準備

ライブラリ読み込み

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

25
27
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
25
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?