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)
}