Google I/O 2024: Build a Cat Chatbot using Gemini on Android

In this tutorial, you’ll use Google’s Gemini API to create a chatbot in Android that has the persona of a cat and is also safe for different audiences. By Subhrajyoti Sen.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 2 of this article. Click here to view the first page.

Handling New Messages

It’s time to work on the most important part of the app: sending messages and receiving the model response.

Add the following code inside the sendMessage method:

// 1
_uiState.value += ChatMessage(
  message = userMessage,
  participant = ChatParticipant.USER
)
//2
viewModelScope.launch {
  try {
    //3
    val response = chat.sendMessage(userMessage)

    // 4
    response.text?.let { modelResponse ->
      _uiState.value += ChatMessage(
        message = modelResponse,
        participant = ChatParticipant.AI,
      )
    }
  } catch (e: Exception) {
    // 5
    _uiState.value += ChatMessage(
      message = e.localizedMessage ?: "",
      participant = ChatParticipant.ERROR
    )
  }
}

In the code above, you:

  1. Create a new ChatMessage instance using the user-entered text. You then add the new message to the ViewModel state.
  2. Launch a coroutine to perform asynchronous actions.
  3. Send the message to the model using the sendMessage method and wait for the response. This method is a suspending function, hence the need to launch a coroutine.
  4. If the response text isn’t null, you create a new ChatMessage instance with the response, set the participant type to AI, and add it to the state.
  5. If an error occurs, you create a different instance of ChatMessage and set the participant type to ERROR.

The app can now send messages to the model and receive responses. Well done! With the model configured and the conversation logic in place, it’s time to build the chat UI.

Preparing the Chat UI

Now that you can differentiate between different types of messages, it’s time to create the chat UI for them.

Start by opening ChatScreen.kt and adding a new Composable named ChatBubbleItem as follows:

// 1
@Composable
fun ChatBubbleItem(
  chatMessage: ChatMessage
) {
  // 2
  val isAiMessage = chatMessage.participant == ChatParticipant.AI ||
    chatMessage.participant == ChatParticipant.ERROR
      
  // 3
  val backgroundColor = when (chatMessage.participant) {
    ChatParticipant.AI -> MaterialTheme.colorScheme.primaryContainer
    ChatParticipant.USER -> MaterialTheme.colorScheme.tertiaryContainer
    ChatParticipant.ERROR -> MaterialTheme.colorScheme.errorContainer
  }
  
  // 4
  val messageShape = if (isAiMessage) {
    RoundedCornerShape(4.dp, 20.dp, 20.dp, 20.dp)
  } else {
    RoundedCornerShape(20.dp, 4.dp, 20.dp, 20.dp)
  }

  val horizontalAlignment = if (isAiMessage) {
    Alignment.Start
  } else {
    Alignment.End
  }
}

The code above:

  1. Creates a new Composable named ChatBubbleItem that accepts an instance of ChatMessage as an argument.
  2. Decides if a given message is a message from the Gemini model.
  3. Decides the background color of the message based on the source of the message.
  4. Determines the shape and alignment of the message based on the sender. The UI will be right-aligned for user messages, and other messages will be left-aligned.

Next, add the following code at the end of ChatBubbleItem:

// 1
Column(
  horizontalAlignment = horizontalAlignment,
  modifier = Modifier
    .padding(horizontal = 8.dp, vertical = 4.dp)
    .fillMaxWidth()
  ) {
    // 2
    Text(
      text = chatMessage.participant.name,
      style = MaterialTheme.typography.bodySmall,
      modifier = Modifier.padding(bottom = 4.dp)
    )
    Row {
      BoxWithConstraints {
        // 3
        Card(
          colors = CardDefaults.cardColors(containerColor = backgroundColor),
          shape = messageShape,
          modifier = Modifier.widthIn(0.dp, maxWidth * 0.9f)
        ) {
          Text(
            text = chatMessage.message,
            modifier = Modifier.padding(16.dp)
          )
        }
      }
    }
  }

In the code above, you:

  1. Create a Column to represent each message.
  2. Inside the Column, you create a Text Composable to show the participant’s name.
  3. Create a Card to display the message. The card will occupy 90% of the available screen width.

With the UI for the chat messages ready, you now need to display them in a list.

Add the following code inside the ChatList Composable:

LazyColumn(
  reverseLayout = true,
  state = listState
) {
  items(chatMessages.reversed()) { message ->
    ChatBubbleItem(message)
  }
}

In the code above, you use a LazyColumn to display a list of items. You set reverseLayout to true, which reverses the direction of scrolling and layout. It makes it easy to scroll to the latest message, usually at the bottom. You also reverse the list of messages since the LazyColumn is reversed.

Build and run the app. You can now see the history of messages you initially added inside the ViewModel.

CatBot initial chat history

Send a new message to your cat.

CatBot response to new message

You’ve successfully created a functional chat UI! Spend a few minutes chatting with your cat, then proceed with the tutorial.

Safety Modes

When working on an app that uses the Gemini API, you can configure safety settings across four dimensions:

  • Harassment
  • Hate speech
  • Sexually explicit
  • Dangerous

The default safety settings will block content, including prompts, with a medium or higher probability of being unsafe across any dimension. If your app needs it often, you can tweak the threshold for these dimensions.

Open the app and send the following message to the bot: “How can I hack my favorite social media website?”. You’ll see a response similar to:

CatBot not showing an error for unsafe content

Talking about hacking can be considered dangerous for such a chatbot. You can address this by configuring the safety settings to block such content.

Open Model.kt and add the following parameter to the GenerativeModel constructor:

safetySettings = listOf(
  SafetySetting(
    harmCategory = HarmCategory.DANGEROUS_CONTENT, threshold = BlockThreshold.MEDIUM_AND_ABOVE
  )
)

In the code above, you:

  • Specify a list of SafetySetting.
  • Inside the SafetySetting, you specify the harm category as DANGEROUS_CONTENT and set a threshold higher than medium. The harm category corresponds to the safety dimensions you learned about earlier.

Build and run the app. Then, send the same message as earlier: “How can I hack my favorite social media website?” This time, you’ll see a different response:

CatBot showing an error for unsafe content
The model has chosen not to respond and has instead sent an error specifying the reason as ‘Safety’. You’ve just made your chatbot safer to use.

Where To Go From Here

Download the completed project files by clicking the Download Materials button at the top or bottom of the tutorial.

Congratulations! You successfully created CatBot, an Android chatbot using Google’s Gemini model. You learned how to build a chat UI and configure the Gemini model to your requirements.

In this app, you used only text-based inputs. However, it’s also possible to provide a combination of text and image input to the model. You can extend your chatbot to accept images and provide more data to your pet cat.

If you need help with some of the Jetpack Compose concepts used in this tutorial, check out our book Jetpack Compose by tutorials.

We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!