PaLM API: Chat quickstart for Android (Kotlin)

Overview

This guide provides the code to get you up and running with the PaLM API on Android. Alternatively, you can download the working source code and open it on Android Studio.

Obtain an API Key

To get started, you'll need to get an API key.

Installing the API Client

These instructions will get the PaLM Java SDK installed in your local Maven repository so that you can add it as a dependency to your Gradle project.

  1. Download the google-cloud-ai-generativelanguage-v1beta3-java.tar.gz file.
  2. Extract the files and install them in mavenLocal:

    # Extract the files
    tar -xzvf google-cloud-ai-generativelanguage-v1beta3-java.tar.gz
    cd google-cloud-ai-generativelanguage-v1beta3-java
    
    # Install to mavenLocal
    ./gradlew publishToMavenLocal
    

Setting up the Android project

  1. In Android Studio, create a new project (if you run into any trouble, see detailed instructions in Create your first Android app).

    • Select “Empty Compose Activity” as the template.
    • Set a name (eg. “Palm App”).
    • Set a package name (eg. com.example.palm.palmapp).
    • Choose Minimum SDK 21 or higher.
  2. Open the AndroidManifest.xml file and add the following permissions:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    
    <!-- Add these permissions -->    
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    
  3. Open the <project_root>/settings.gradle file and add mavenLocal() to the repositories block within dependencyResolutionManagement:

    pluginManagement {
        // ...
    }
    dependencyResolutionManagement {
        repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
        repositories {
            google()
            mavenCentral()
            // Add this line
            mavenLocal()
        }
    }
    
  4. Open your Gradle configuration file (<project_root>/app/build.gradle), be sure to exclude the INDEX.LIST and DEPENDENCIES files within packagingOptions, and add the necessary libraries to the dependencies block:

      // build.gradle
      // ...
      android {
          // ...
          packagingOptions {
              resources {
                  excludes += '/META-INF/{AL2.0,LGPL2.1}'
                  excludes += 'META-INF/INDEX.LIST' // Add this line
                  excludes += 'META-INF/DEPENDENCIES' // And this line
              }
          }
      }
    
      dependencies {
          // ... other androidx dependencies
    
          // add these dependencies to use Generative AI
          implementation("com.google.cloud:gapic-google-cloud-ai-generativelanguage-v1beta3-java:0.0.0-SNAPSHOT")
          implementation("io.grpc:grpc-okhttp:1.53.0")
          // this one is needed by Android's Jetpack Compose
          implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1")
      }
    
    
  5. After adding the dependencies, sync your Android project with Gradle files.

Generating Messages

Create a new file named MainViewModel.kt and create a basic ViewModel. This is the file where you'll add all the functions to interact with the API.

class MainViewModel : ViewModel() {
    // you'll add the functions below to this class
}

1. Initialize the Discuss Service Client

Initialize a DiscussServiceClient by passing your API Key as a header to the TransportChannelProvider to be used by DiscussServiceSettings:

private fun initializeDiscussServiceClient(
    apiKey: String
): DiscussServiceClient {
    // (This is a workaround because GAPIC java libraries don't yet support API key auth)
    val transportChannelProvider = InstantiatingGrpcChannelProvider.newBuilder()
        .setHeaderProvider(FixedHeaderProvider.create(hashMapOf("x-goog-api-key" to apiKey)))
        .build()

    // Create DiscussServiceSettings
    val settings = DiscussServiceSettings.newBuilder()
        .setTransportChannelProvider(transportChannelProvider)
        .setCredentialsProvider(FixedCredentialsProvider.create(null))
        .build()

    // Initialize a DiscussServiceClient
    val discussServiceClient = DiscussServiceClient.create(settings)

    return discussServiceClient
}

2. Create a Message Prompt

You need to provide a MessagePrompt to the API so that it can predict what is the next message in the discussion.

2.1. (optional) Create some examples

Optionally, you can provide some examples of what the model should generate. This includes both user input and the response that the model should emulate.

private fun createCaliforniaExample(): Example {
    val input = Message.newBuilder()
        .setContent("What is the capital of California?")
        .build()

    val response = Message.newBuilder()
        .setContent("If the capital of California is what you seek, Sacramento is where you ought to peek.")
        .build()

    val example = Example.newBuilder()
        .setInput(input)
        .setOutput(response)
        .build()

    return example
}

2.2. Create the prompt

Pass the examples to the MessagePrompt.Builder along with the current message history and optionally the example from the previous step.

private fun createPrompt(
    messageContent: String
): MessagePrompt {
    val palmMessage = Message.newBuilder()
        .setAuthor("0")
        .setContent(messageContent)
        .build()

    val messagePrompt = MessagePrompt.newBuilder()
        .addMessages(palmMessage) // required
        .setContext("Respond to all questions with a rhyming poem.") // optional
        .addExamples(createCaliforniaExample()) // use addAllExamples() to add a list of examples
        .build()

    return messagePrompt
}

3. Generate Messages

3.1 Create a GenerateMessageRequest

Create a GenerateMessageRequest by passing a model name and prompt to the GenerateMessageRequest.Builder:

private fun createMessageRequest(prompt: MessagePrompt): GenerateMessageRequest {
    return GenerateMessageRequest.newBuilder()
        .setModel("models/chat-bison-001") // Required, which model to use to generate the result
        .setPrompt(prompt) // Required
        .setTemperature(0.5f) // Optional, controls the randomness of the output
        .setCandidateCount(1) // Optional, the number of generated messages to return
        .build()
}

3.2 Send the request

The API currently only provides blocking synchronous methods, so consider making this call in a coroutine scope (eg. viewModelScope) to suspend its execution:

private fun generateMessage(
        request: GenerateMessageRequest
) {
    viewModelScope.launch(Dispatchers.IO) {
        try {
            val response = client.generateMessage(request)
            val returnedMessage = response.candidatesList.last()
            // display the returned message in the UI
        } catch (e: Exception) {
            // API returned an error
        }
    }
}

4. Continue conversations

In order to continue the conversation, you can call createPrompt() once again with the new user input, and then generate the message again:

fun sendMessage(userInput: String) {
    val prompt = createPrompt(userInput)

    val request = createMessageRequest(prompt)
    generateMessage(request)
}

5. Put it all together

  1. In the MainViewModel class, create a StateFlow of messages that will be exposed to the UI, and update the generateMessage() function to emit updates to that flow:

    class MainViewModel : ViewModel() {   
      // Add the StateFlow
      private val _messages = MutableStateFlow<List<Message>>(value = listOf())
      val messages: StateFlow<List<Message>>
          get() = _messages
    
      // ... (all the functions we created in previous steps)
    
      private fun generateMessage(
          request: GenerateMessageRequest
      ) {
          viewModelScope.launch(Dispatchers.IO) {
              try {
                  val response = client.generateMessage(request)
    
                  val returnedMessage = response.candidatesList.last()
                  // display the returned message in the UI
                  _messages.update {
                      // Add the response to the list
                      it.toMutableList().apply {
                          add(returnedMessage)
                      }
                  }
              } catch (e: Exception) {
                  // There was an error, let's add a new message with the details
                  _messages.update { messages ->
                      val mutableList = messages.toMutableList()
                      mutableList.apply {
                          add(
                              Message.newBuilder()
                                  .setAuthor("API Error")
                                  .setContent(e.message)
                                  .build()
                          )
                      }
                  }
              }
          }
      }
    }
    
  2. Create the DiscussServiceClient, a MessagePrompt and a GenerateMessageRequest to send the first request in the MainViewModel's initializer block. Note that you'll need to pass your own API key below:

      class MainViewModel : ViewModel() {
          // (state flow declaration omitted for brevity)
    
          // Add this variable
          private var client: DiscussServiceClient
    
          init {
              // Initialize the Discuss Service Client
              client = initializeDiscussServiceClient(
                  apiKey = "<insert your api key here>"
              )
    
              // Create the message prompt
              val prompt = createPrompt("How tall is the Eiffel Tower?")
    
              // Send the first request to kickstart the conversation
              val request = createMessageRequest(prompt)
              generateMessage(request)
          }
    
          // (other functions omitted for brevity)
      }
    
  3. Update the createPrompt() function to display new messages in the UI when they're sent:

    private fun createPrompt(
        messageContent: String
    ): MessagePrompt {
        val palmMessage = // ... (omitted for brevity)
    
        // Add this line to update the UI
        _messages.update {
            it.toMutableList().apply {
                add(palmMessage)
            }
        }
    
        val messagePrompt = // ... (omitted for brevity)
    
        return messagePrompt
    }
    

A sample UI

You can use the sample UI below to test your implementation. Open the MainActivity.kt file and add the SampleUI function below.

package com.example.palm.palmapp

// (imports at the top of the file) 
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.ai.generativelanguage.v1beta3.Message

// ...

@Composable
fun SampleUi(
    mainViewModel: MainViewModel = viewModel()
) {
    val (inputText, setInputText) = remember { mutableStateOf("") }
    val messages: List<Message> by mainViewModel.messages.collectAsState()
    Column(
        modifier = Modifier.padding(all = 16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        OutlinedTextField(
            modifier = Modifier.fillMaxWidth(),
            value = inputText,
            onValueChange = setInputText,
            label = { Text("Input:") }
        )
        Button(
            onClick = {
                mainViewModel.sendMessage(inputText)
            },
            modifier = Modifier.padding(8.dp)
        ) {
            Text("Send Message")
        }
        LazyColumn(
            modifier = Modifier.fillMaxWidth(),
            reverseLayout = true
        ) {
            items(messages) { message ->
                Card(modifier = Modifier.padding(vertical = 2.dp)) {
                    Column(
                        modifier = Modifier.padding(8.dp)
                    ) {
                        Text(
                            text = message.author,
                            style = MaterialTheme.typography.caption,
                            color = MaterialTheme.colors.secondary
                        )
                        Text(
                            modifier = Modifier.fillMaxWidth(),
                            text = message.content,
                            style = MaterialTheme.typography.body1
                        )
                    }
                }
            }
        }
    }
}

And call this function from the onCreate function of your MainActivity, replacing the call to Greeting() inside Surface:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            PalmAppTheme { // <- this might have a different name in your app
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colors.background
                ) {
                    SampleUi() // <- Replace Greeting("Android") with this
                }
            }
        }
    }
}

Run the app

Hit the ▶️ button to run the app on a physical device or emulator. You should see a UI with an input text field, a "Send Message" button, and a list of messages:

App UI