0

Firebase Tutorial: Real-time Chat part 2

Tiếp theo từ phần: https://viblo.asia/thevinh92/posts/rEBRAKprG8Zj

Ở phần 1 chúng ta đã hoàn thiện phần UI của app Chat, phần này sẽ làm việc với Firebase.

Firebase Data Structure

Firebase database là kiểu NoSQL JSON. Về cơ bản, mọi thứ trong Firebase database là 1 JSON object, và mỗi key của 1 object có riêng 1 URL của nó. Ví dụ:

{
  // https://<my-firebase-app>.firebaseio.com/messages
  "messages": {
    "1": { // https://<my-firebase-app>.firebaseio.com/messages/1
      "text": "Hey person!", // https://<my-firebase-app>.firebaseio.com/messages/1/text
      "senderId": "foo" // https://<my-firebase-app>.firebaseio.com/messages/1/senderId
    },
    "2": {
      "text": "Yo!",
      "senderId": "bar"
    },
    "2": {
      "text": "Yo!",
      "senderId": "bar"
    },
  }
}

Firebase ủng hộ kiểu denormalized data structure - tức là bạn có thể sẽ bị duplicate 1 số data nhưng đổi lại data sẽ được truy nhập nhanh hơn - vì vậy senderId sẽ được chứa trong mỗi message.

Setting Up the Firebase Reference

Thêm properties vào ChatViewController.swift:

 let rootRef = Firebase(url: "https://<my-firebase-app>.firebaseio.com/")
var messageRef: Firebase!

init messageRef trong viewDidLoad():

override func viewDidLoad() {
  super.viewDidLoad()
  title = "ChatChat"
  setupBubbles()
  // No avatars
  collectionView!.collectionViewLayout.incomingAvatarViewSize = CGSizeZero
  collectionView!.collectionViewLayout.outgoingAvatarViewSize = CGSizeZero
  messageRef = rootRef.childByAppendingPath("messages")
}

Create rootRef để tạo ra 1 conection tới Firebase database. Sau đó create messageRef thông qua childByAppendingPath() - 1 helper method để tạo ra child reference. Khi bạn tạo 1 reference mới không có nghĩa là bạn tạo 1 connection mới, tất cả các reference vẫn chia sẻ chung connection tới cùng Firebase database.

Sending Messages

Ở thời điểm này, nếu bạn nhấn vào button "Send" bạn sẽ thấy app bị crash, bây giờ bạn đã kết nối được đến Firebase database, bạn sẽ gửi được message thực sự. đầu tiên, hãy xoá những test messages ở trong viewDidAppear(_😃, rồi override methode bên dưới để khiến cho khi bạn nhấn "Send", message sẽ được save vào Firebase database.

 override func didPressSendButton(button: UIButton!, withMessageText text: String!, senderId: String!,
    senderDisplayName: String!, date: NSDate!) {

  let itemRef = messageRef.childByAutoId() // 1
  let messageItem = [ // 2
    "text": text,
    "senderId": senderId
  ]
  itemRef.setValue(messageItem) // 3

  // 4
  JSQSystemSoundPlayer.jsq_playMessageSentSound()

  // 5
  finishSendingMessage()
}
  • Sử dụng childByAutoId(), để tạo 1 child reference với 1 unique key.
  • Tạo ra 1 dictionary để thể hiện message. [String: AnyObject] hoạt động như 1 object kiểu JSON
  • Lưu giá trị của message item tại địa chỉ itemRef
  • Phát ra âm thanh "message sent"
  • Hoàn thiện hành động "send" và reset lại input toolbar về empty

Build&Run, thử send message trong app và bạn sẽ thấy messages xuất hiện trên dashboard trong thời gian thực:

8-ChatChatAddMessage.gif

Hiện tại messages vẫn chưa xuât hiện trên app, chúng ta sẽ tiếp tục với phần đó

Real Time Data Synchronization with Firebase

Mỗi khi bạn update data trong Firebase database, database sẽ gửi update tới bất cứ app nào connect đến. Đồng bộ dữ liệu trong firebase hoạt động trong 3 phần: a URL, an event, and a snapshot.

Ví dụ, đoạn code ở dưới là cách để bạn quan sát 1 message:

let ref = Firebase(url: "https://<my-firebase-app>.firebaseio.com/messages") // 1
ref.observeEventType(.ChildAdded) { (snapshot: FDataSnapshot!) in { // 2
  print(snapshot.value) // 3
}
  • Sử dụng Firebase App URL, bạn tạo ra 1 reference tới Firebase Database. URL bạn truyền vào sẽ trỏ tới data mà bạn muốn đọc.
  • Gọi method observeEventType(eventType: FEventType, withBlock: ((FDataSnapshot!) -> Void)!), với FEventType.ChildAdded truyền vào. child-added event (the event) sẽ được bắn ra cho mỗi item ở URL đó.
  • Bên trong closure sẽ nhận được một FDataSnapshot (the snapshot) mà chứa data và những methods cần thiết khác.

Synchronizing the Data Source

Thêm đoạn code sau vào ChatViewController:

 private func observeMessages() {
  // 1
  let messagesQuery = messageRef.queryLimitedToLast(25)
  // 2
  messagesQuery.observeEventType(.ChildAdded) { (snapshot: FDataSnapshot!) in
    // 3
    let id = snapshot.value["senderId"] as! String
    let text = snapshot.value["text"] as! String

    // 4
    self.addMessage(id, text: text)

    // 5
    self.finishReceivingMessage()
  }
}
  • Bắt đầu với việc tạo 1 query và hạn chế việc synchronization tới 25 messages cuối.
  • Sử dụng .ChildAdded event để observe mỗi một child item được add tại location của mesages. Block này sẽ được trigger khi khởi tạo dữ liệu và mỗi khi mà data thay đổi (khi send 1 message mới)
  • lấy ra giá trị của senderId và text từ snapshot.value
  • gọi method addMessage() để add message mới vào data source
  • Thông báo tới JSQMessageViewController rằng message đã được nhận về.

Tiếp theo, gọi observeMessages() trong viewDidAppear(_😃:

 override func viewDidAppear(animated: Bool) {
  super.viewDidAppear(animated)
  observeMessages()
}

Build&Run app, bạn sẽ thấy những message cũ cũng sẽ hiện lên cùng với những message bạn mới thêm vào:

Screen-Shot-2015-12-28-at-9.00.08-PM-275x500.png

Knowing When a User is Typing

Một trong những tính năng ngầu nhất ở 1 Messages app đó là nhìn thấy biểu tượng "user is typing". Khi 1 buble pops up, bạn sẽ biết rằng 1 user khác đang typing. Biểu tượng này rất rất quan trọng, bởi vì nó giúp chúng ta ko phải gửi những messages khó xử như kiểu "Are you still there?"

Có nhiều cách để phát hiện user đang typing, nhưng cách tốt nhất để check là ở bên trong textViewDidChange(_:textView), ví dụ như:

override func textViewDidChange(textView: UITextView) {
  super.textViewDidChange(textView)
  // If the text is not empty, the user is typing
  print(textView.text != "")
}

Để biết được khi nào thì user typing, ta sẽ check giá trị của textView.text. Nếu giá trị ko phải là emty string thì bạn sẽ biết user đang nhập text. Sử dụng Firebase, chúng ta có thể update databse khi 1 user đang type, rồi thông qua đó, bạn có thể hiển thị biểu tượng và thông báo cho các user khác.

Hãy thêm nhưgnx property sau vào ChatViewController:

var userIsTypingRef: Firebase! // 1
private var localTyping = false // 2
var isTyping: Bool {
  get {
    return localTyping
  }
  set {
    // 3
    localTyping = newValue
    userIsTypingRef.setValue(newValue)
  }
}
  • Chúng ta cần 1 reference để track xem khi nào local user typing
  • Lưu trữ trạng thái typing của local user trong 1 private property
  • Mỗi khi trạng thái typing của local user thay đổi bạn sẽ update thông qua userIsTypingRef thông qua hàm set

Thêm method sau vào ChatViewController:

private func observeTyping() {
  let typingIndicatorRef = rootRef.childByAppendingPath("typingIndicator")
  userIsTypingRef = typingIndicatorRef.childByAppendingPath(senderId)
  userIsTypingRef.onDisconnectRemoveValue()
}

Method này tạo ra 1 reference tới URL của /typingIndicator, là nơi mà bạn sẽ update trạng thái typing của user. Bạn sẽ không muốn dữ liệu này vẫn còn tồn tại khi user đã log out, vì vậy bạn có thể delete nó khi user rời đi bằng cách sử dụng onDisconnectRemoveValue()

Update hàm viewDidAppear(_😃 để gọi tới observeTyping()

override func viewDidAppear(animated: Bool) {
  super.viewDidAppear(animated)
  observeMessages()
  observeTyping()
}

Tiếp theo, thêmm method textViewDidChange(_:textView) vào ChatViewController và set isTyping mỗi khi có thay đổi:

override func textViewDidChange(textView: UITextView) {
  super.textViewDidChange(textView)
  // If the text is not empty, the user is typing
  isTyping = textView.text != ""
}

Cuối cùng, reset giá trị isTyping khi bạn nhấn nut "Send" bằng cách thêm "isTyping = false" vào dòng cuối của method didPressSendButton(_:withMessageText:senderId:senderDisplayName📅)

Build&Run app, đồng thời mở Firebase App Dashboard để xem dữ liệu. Khi bạn type message, bạn sẽ thấy typingIndicator record update for user: 11-TypingIdicatorDta.gif

Querying for Typing Users

Chỉ thị "user is typing" sẽ hiện lên mỗi khi 1 người dùng nào đó typing, trừ local user. Đương nhiên, ai chẳng biết rằng mình đang typing =)). Sử dụng Firebase query, bạn có thể lấy về tất các các users đang typing. Thêm property sau vào ChatViewController:

 var usersTypingQuery: FQuery!

Property này giữ 1 FQuery, tượng tự như 1 Firbase reference, trừ việc nó phục vụ cho 1 order function. Tiếp theo, thay đổi method observeTyping()

 private func observeTyping() {
  let typingIndicatorRef = rootRef.childByAppendingPath("typingIndicator")
  userIsTypingRef = typingIndicatorRef.childByAppendingPath(senderId)
  userIsTypingRef.onDisconnectRemoveValue()

  // 1
  usersTypingQuery = typingIndicatorRef.queryOrderedByValue().queryEqualToValue(true)

  // 2
  usersTypingQuery.observeEventType(.Value) { (data: FDataSnapshot!) in

    // 3 You're the only typing, don't show the indicator
    if data.childrenCount == 1 && self.isTyping {
      return
    }

    // 4 Are there others typing?
    self.showTypingIndicator = data.childrenCount > 0
    self.scrollToBottomAnimated(true)
  }

}
  • Init query bằng việc lấy về tất cả users mà đang typing. Nó cơ bản có thể hiểu:"Ê, Firebase, tìm tới key /typingIndicator và lấy về cho tao tất cả các user mà có value là true"
  • Tương tự như ở method observeMessage, ở đây chúng ta observe việc thay đổi bằng cách sử dụng .Value, nó sẽ update mỗi khi giá trị thay đổi.
  • Bạn cần phải biết có bao nhiêu users trong query, nếu chỉ có 1 user, check xem nó có phải là local user (bản thân mình) hay ko, nếu đúng thì ko hiểu thị biểu tượng đang typing.
  • Nếu Có nhiều hơn 0 users, và local user ko phải đang type thì chugns ta sẽ hiểu thị "some user is typing". Gọi self.scrollToBottomAnimated(_:animated:) để chắc chắn người dùng sẽ nhìn thấy.

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí