BlindlyTeam/Blindly

View on GitHub
app/src/main/java/ch/epfl/sdp/blindly/database/DatabaseHelper.kt

Summary

Maintainability
A
0 mins
Test Coverage
F
53%
package ch.epfl.sdp.blindly.database

import android.util.Log
import ch.epfl.sdp.blindly.main_screen.my_matches.chat.Message
import ch.epfl.sdp.blindly.location.BlindlyLatLng
import com.google.firebase.database.*

/**
 * Class to be injected to provide the firebase live database
 *
 */
class DatabaseHelper {
    companion object {
        private const val DB_URL =
            "https://blindly-24119-default-rtdb.europe-west1.firebasedatabase.app/"
        private const val CHAT_DB_NAME = "messages"
        private const val LOCATION_DB_NAME = "locations"

        /**
         * Get the conversation id between two users
         *
         * @param userId the logged-in user
         * @param otherUserId the user to converse with
         * @return the conversation id
         */
        private fun getConversationId(userId: String, otherUserId: String): String {
            //this is done to get the same chatId from both sides
            return if (userId < otherUserId) {
                "($userId, $otherUserId)"
            } else {
                "($otherUserId, $userId)"
            }
        }
    }

    private val databaseInstance = FirebaseDatabase.getInstance(DB_URL)

    /**
     * The live database for the chat
     *
     * @param userId the logged-in user id
     * @param otherUserId the other user's id to send message to
     * @return The live database
     */
    fun getChatLiveDatabase(
        userId: String,
        otherUserId: String
    ): ChatLiveDatabase {
        return ChatLiveDatabase(
            databaseInstance.getReference(CHAT_DB_NAME)
                .child(getConversationId(userId, otherUserId)),
            userId
        )
    }

    /**
     * The live database for the location
     *
     * @param userId the logged-in user id
     * @param otherUserId the other user's id to share the location with
     * @return The live database
     */
    fun getLocationLiveDatabase(
        userId: String,
        otherUserId: String
    ): LocationLiveDatabase {
        return LocationLiveDatabase(
            databaseInstance.getReference(LOCATION_DB_NAME)
                .child(getConversationId(userId, otherUserId)),
            userId
        )
    }

    /**
     * The live database to share the location live
     *
     * @constructor
     * Internal constructor to get an instance see [getLocationLiveDatabase]
     *
     * @param dr the firebase database reference
     * @param userId the logged-in user id
     */
    class LocationLiveDatabase internal constructor(dr: DatabaseReference, userId: String) :
        BlindlyLiveDatabase<BlindlyLatLng>(dr, userId) {
        override val typeIndicator = object : GenericTypeIndicator<Message<BlindlyLatLng>>() {}

        fun updateLocation(location: BlindlyLatLng) {
            val newMessage = Message(
                location,
                userId
            )
            // We keep only one entry
            dr.child(userId).setValue(newMessage)
        }
    }

    /**
     * The live database for the chat
     *
     * @constructor
     * Internal constructor to get an instance see [getChatLiveDatabase]
     *
     * @param dr the firebase database reference
     * @param userId the logged-in user id
     */
    class ChatLiveDatabase internal constructor(dr: DatabaseReference, userId: String) :
        BlindlyLiveDatabase<String>(dr, userId) {
        override val typeIndicator = object : GenericTypeIndicator<Message<String>>() {}

        fun sendMessage(message: String) {
            val newMessage = Message(
                message,
                userId
            )
            dr.child(newMessage.timestamp.toString()).setValue(newMessage)
        }
    }

    /**
     * A generic live database using [Message] to communicate to other users
     *
     * @param T The type contained in messages, see [Message]
     * @constructor
     * Internal constructor to create an instance, use one of the public methods to
     * get an instance
     *
     * @param dr the firebase database reference
     * @param userId the logged-in user id
     */
    abstract class BlindlyLiveDatabase<T> internal constructor(
        protected val dr: DatabaseReference,
        protected val userId: String
    ) {
        // Init at one as most of the time we don't register more than one listener per db
        private val eventListeners: MutableList<EventListener<T>> = ArrayList(1)

        // We need to indicate type on each class initialization (=runtime) as
        // the JVM has type-ereasure on runtime
        abstract val typeIndicator: GenericTypeIndicator<Message<T>>

        init {
            // Add the event listener that dispatches to other listeners
            dr.addChildEventListener(object : ChildEventListener {
                override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
                    val message = snapshot.getValue(typeIndicator)
                    if (message != null) {
                        eventListeners.forEach { eventListener ->
                            eventListener.onMessageReceived(message)
                        }
                    } else {
                        Log.w("LiveDatabaseHelper", "Unhandled message received")
                    }
                }

                // we don't allow any changes, removals etc. so these stay only for the compilation
                override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
                    val message = snapshot.getValue(typeIndicator)
                    if (message != null) {
                        eventListeners.forEach { eventListener ->
                            eventListener.onMessageUpdated(message)
                        }
                    } else {
                        Log.w("LiveDatabaseHelper", "Unhandled message updated")
                    }
                }

                override fun onChildRemoved(snapshot: DataSnapshot) {
                    TODO("Not yet implemented")
                }

                override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
                    TODO("Not yet implemented")
                }

                override fun onCancelled(error: DatabaseError) {
                    //TODO("Not yet implemented")
                    Log.e("LiveDatabaseHelper", "Operation cancelled: ", error.toException())
                }


            })
        }

        /**
         * Add an event listener on this live database
         *
         * @param listener A listener to register
         */
        fun addListener(listener: EventListener<T>) {
            eventListeners.add(listener)
        }

        /**
         * Remove an event listener on this live database
         *
         * @param listener The listener to remove
         */
        @Suppress("unused")
        fun removeListener(listener: EventListener<T>) {
            eventListeners.remove(listener)
        }

        /**
         * The event listener for Blindly live databases
         *
         * @param T the type contained in the messages, it must match [BlindlyLiveDatabase] message type
         */
        abstract class EventListener<T> {
            abstract fun onMessageReceived(message: Message<T>)
            open fun onMessageUpdated(message: Message<T>) {
                TODO("Not yet implemented")
            }
        }
    }
}