Skip to content

Firebase Chat with Smart Replies (Cloud Firestore & ML Kit)

With the rise of AI becoming a norm in modern apps, many AI-based features are becoming standard among competent apps. Among these is Smart Replies.

Smart Replies is the ability to generate potential replies to a conversation based on its context and the messages that precede it. The user saves precious time as a reply can be done in a single tap rather than typing it in.

This feature is used by major apps including Gmail and LinkedIn. While you won’t find that level of Smart Reply generation, you might still get good use out of it in your app.

Firebase Smart Replies is great for the fact that it can run completely on the client. No need to send conversations to a remote server. It isn’t without its limitations though.

We’ll build on top of the Firestore Chat Application we previously built. We’ll add this feature to generate smart replies whenever a message is received from another user.

Install the Dependencies

First you’ll need to include these in your app/build.gradle file.

implementation 'com.google.firebase:firebase-ml-natural-language:18.1.1'
implementation 'com.google.firebase:firebase-ml-natural-language-language-id-model:18.0.2'
implementation 'com.google.firebase:firebase-ml-natural-language-smart-reply-model:18.0.0'

Additionally for the Smart Reply model, you also need to disable tflite compression in your app/build.gradle.

android {
    // ...
    aaptOptions {
        noCompress "tflite"
    }
}

Preparing the Buttons

We’ll be adding smart replies to our conversations that occur in RoomActivity from the Firestore Chat. If you haven’t seen that, you can view the source code below.

Start by editing the XML layout file. I used 3 buttons instead of any sort of list because we’ll only have up to 3 replies generated at a time.

<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/layout_smart_reply"
    android:layout_width="match_parent"
    android:visibility="visible"
    android:layout_height="56dp"
    android:paddingTop="8dp"
    android:paddingBottom="8dp">

    <Button
        android:id="@+id/button_reply_1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        style="@style/ButtonSmartReply"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@id/button_reply_2"
        tools:text="Hello world" />

    <Button
        android:id="@+id/button_reply_2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        style="@style/ButtonSmartReply"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        tools:text="Wot u sayin bruv" />

    <Button
        android:id="@+id/button_reply_3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        style="@style/ButtonSmartReply"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/button_reply_2"
        tools:text="Squad fam" />

</androidx.constraintlayout.widget.ConstraintLayout>

Then in RoomActivity, to make things easy, I put the buttons into a list I can iterate through.

val smartReplyButtons = ArrayList<Button>()

private fun setViewListeners() {

    button_send.setOnClickListener {
        sendChatMessage(edittext_chat.text.toString())
        edittext_chat.setText("")
    }

    smartReplyButtons.add(button_reply_1)
    smartReplyButtons.add(button_reply_2)
    smartReplyButtons.add(button_reply_3)

    for (i in 0 until smartReplyButtons.size) {
        smartReplyButtons[i].setOnClickListener { sendChatMessage(suggestions[i].text) }
    }
}

Generating the Smart Replies

We need 3 more global variables. The instance of FirebaseNaturalLanguage, the conversation list which we pass in to the FirebaseNaturalLanguage, and the suggestions we get from it.

val firebaseNaturalLanguage = FirebaseNaturalLanguage.getInstance()
val conversation = ArrayList<FirebaseTextMessage>()
val suggestions = ArrayList<SmartReplySuggestion>()

Finally, where the magic happens. Add items to your conversation list, specifying which messages come from the local user or other users.

private fun listenForChatMessages() {
    ...
    // Generate Smart Reply
    for (message in chatMessages)
        if (message.user == user!!.uid)
            conversation.add(
                    FirebaseTextMessage.createForLocalUser(
                            message.text,
                            message.timestamp.time
                    ))

        else
            conversation.add(
                    FirebaseTextMessage.createForRemoteUser(
                            message.text,
                            message.timestamp.time,
                            message.user
                    ))
}

Then identify if the last message in your conversation was sent by a local or remote user, and if it is the local user, generate your replies.

if (chatMessages.last().user != user!!.uid) {

    val smartReply = firebaseNaturalLanguage.smartReply

    smartReply.suggestReplies(conversation)
            .addOnSuccessListener {
                layout_smart_reply.visibility = View.VISIBLE
                suggestions.clear()
                suggestions.addAll(it.suggestions)
                
                for (i in 0 until 3)
                    if (suggestions.size >= i) {
                        smartReplyButtons[i].text = suggestions[i].text
                        smartReplyButtons[i].visibility = View.VISIBLE
                    } else {
                        smartReplyButtons[i].visibility = View.GONE
                    }
            }

            .addOnFailureListener {
                Log.e("FirebaseSmartReply", "Error getting suggestions: ${it.message}")
                it.printStackTrace()
            }
} else {
    layout_smart_reply.visibility = View.GONE
}

Smart Replies but not smart enough yet

That’s really all there is to it. With that, you should be able to generate smart replies in your chat.

Like I said earlier, the responses may not exactly be as ‘smart’ as Gmail’s or LinkedIn’s model, but it’s what we got.

You can get source code here on Github. It builds on the Firebase Chat Application we covered previously, but I decided to make it into its own separate repository.

“Eric that’s great and all, but can we get some architecture with this? I want actual quality code”

Hmmm interesting point. Let me take a week to generate a response…