caipivara
7/20/2016 - 7:01 PM

Android / Firebase Realtime Database - chat example

Android / Firebase Realtime Database - chat example

ext.firebase = "9.2.1"

ext.deps = [
  firebaseCore        : "com.google.firebase:firebase-core:$firebase",
  firebaseDatabase    : "com.google.firebase:firebase-database:firebase",
  firebaseUi          : 'com.firebaseui:firebase-ui-database:0.4.2'
]
{
  "channels" : {
    "channelIdValue" : { // Cloned inside bc didnt find a way to get it from here
      "channelId" : "channelIdValue",
      "ownerId" : "...",
      "ownerName" : "...",
      "requesterId" : "...",
      "requesterName" : "...",
      "title" : "...",
      "updatedTimeStamp" : 1468598723075 //Automatic number from Firebase with ServerValue.TIMESTAMP
    }
  },
  "messages" : {
    "-KMjIWSwDlZNDPFVQmFw" : { //Automatic id generated with Firebase push
      "channelId" : "channelIdValue",
      "ownerId" : "...",
      "ownerName" : "...",
      "requesterId" : "...",
      "requesterName" : "...",
      "text" : "...",
      "updatedTimeStamp" : 1468598720589   //Automatic number from Firebase with ServerValue.TIMESTAMP
    }
  },
  "users" : {
    "userIdValue" : {
      "channels" : {
        "channelIdValue" : { // Cloned inside bc didnt find a way to get it from here
          "channelId" : "channelIdValue",
          "ownerId" : "...",
          "ownerName" : "...",
          "title" : "..."
        }
      },
      "name" : "...",
      "updatedTimeStamp" : 1468598727805 //Automatic number from Firebase with ServerValue.TIMESTAMP
    }
  }
}

channelIdValue = MyVideo.id-MyRequeser.id
userIdValue = My user id
class MyFirebaseDatabase() {
  val channelIdKey = "channelId"
  val usersReference: DatabaseReference
    get() = FirebaseDatabase.getInstance().reference.child("users")

  val channelsReference: DatabaseReference
    get() = FirebaseDatabase.getInstance().reference.child("channels")

  companion object {
    fun enablePersistence() {
      try {
        FirebaseDatabase.getInstance().setPersistenceEnabled(true)
      } catch (e: DatabaseException) {
        Timber.i(e, "Firebase database persistence is already enabled.")
      }
    }

    fun enabledDebug() {
      FirebaseDatabase.getInstance().setLogLevel(Logger.Level.INFO)
    }
  }

  fun queryMessages(channelId: String): Query = FirebaseDatabase.getInstance().reference
      .child("messages")
      .orderByChild(channelIdKey)
      .equalTo(channelId)

  fun queryChannels(user: User): DatabaseReference {
    return usersReference.child(user.id).child("channels")
  }

  fun push(setup: ChannelSetup, text: String) {
    val singleUserRef = usersReference.child(setup.requesterId)

    val ownerMap = mapOf(Pair("name", setup.ownerName),
        Pair("updatedTimeStamp", ServerValue.TIMESTAMP))

    val requesterMap = mapOf(Pair("name", setup.requesterName),
        Pair("updatedTimeStamp", ServerValue.TIMESTAMP))

    val userChannelsMap = mapOf(Pair(channelIdKey, setup.channelId),
        Pair("ownerName", setup.ownerName),
        Pair("ownerId", setup.ownerId),
        Pair("title", setup.channelTitle))

    val channelMap = mapOf(Pair("title", setup.channelTitle),
        Pair("ownerId", setup.ownerId),
        Pair("ownerName", setup.ownerName),
        Pair("requesterId", setup.requesterId),
        Pair("requesterName", setup.requesterName),
        Pair(channelIdKey, setup.channelId),
        Pair("updatedTimeStamp", ServerValue.TIMESTAMP))

    val messageMap = mapOf(Pair("text", text),
        Pair("requesterId", setup.requesterId),
        Pair("requesterName", setup.requesterName),
        Pair("ownerId", setup.ownerId),
        Pair("ownerName", setup.ownerName),
        Pair("updatedTimeStamp", ServerValue.TIMESTAMP),
        Pair(channelIdKey, setup.channelId))

    // TODO: Check if get better using transactions instead of RXJava
    Observable.zip(
        FirebaseDatabase.getInstance().reference.child("messages").push().setValueObservable(messageMap),
        channelsReference.child(setup.channelId).updateChildrenObservable(channelMap),
        singleUserRef.child("channels")?.child(setup.channelId)?.updateChildrenObservable(userChannelsMap),
        singleUserRef.updateChildrenObservable(ownerMap),
        singleUserRef.updateChildrenObservable(requesterMap), { t1, t2, t3, t4, t5 -> })
        .composeForBackgroundTasks()
        .subscribe({}, { Timber.e(it, "Something happened with Firebase DB push") })
  }

}
fun DatabaseReference.setValueObservable(any: Any): Observable<FirebaseAnswer<DatabaseReference>> {
  return Observable.create {
    setValue(any, DatabaseReference.CompletionListener { error, databaseReference ->
      it.onNext(FirebaseAnswer(body = databaseReference, error = error))
      it.onCompleted()
    })
  }
}

fun DatabaseReference.updateChildrenObservable(map: Map<String, *>): Observable<FirebaseAnswer<DatabaseReference>> {
  return Observable.create {
    updateChildren(map, { error, databaseReference ->
      it.onNext(FirebaseAnswer(body = databaseReference, error = error))
      it.onCompleted()
    })
  }
}

class FirebaseAnswer<out T>(val body: T? = null, val error: DatabaseError? = null)
class ChannelsAdapter(chatsQuery: Query, val user: User)
: FirebaseRecyclerAdapter<Channel, ChatsViewHolder>(
    Channel::class.java, R.layout.chats_item, ChatsViewHolder::class.java, chatsQuery) {

  override fun populateViewHolder(viewHolder: ChatsViewHolder?, channel: Channel?, position: Int) {
    viewHolder?.itemView?.chatNameText?.text = channel?.title
  }

  class ChatsViewHolder(v: View) : RecyclerView.ViewHolder(v)
}
class ChannelAdapter(messagesQuery: Query, val user: User)
: FirebaseRecyclerAdapter<Message, ChannelAdapter.MessageViewHolder>(
    Message::class.java,
    R.layout.channel_item,
    ChannelAdapter.MessageViewHolder::class.java,
    messagesQuery) {

  override fun populateViewHolder(viewHolder: ChannelAdapter.MessageViewHolder, message: Message, i: Int) {
    viewHolder.itemView.messageText.text = message.text

    if (user.id == message.ownerId) {
      viewHolder.itemView.authorText.text = message.ownerName
    } else {
      viewHolder.itemView.authorText.text = viewHolder.itemView.resources.getText(R.string.me)
    }
  }

  class MessageViewHolder(v: View) : RecyclerView.ViewHolder(v)
}