PaLM API: Text quickstart for Android (Kotlin)

Overview

This topic shows you how to get up and running with the PaLM API on Android. This guide assumes you are familiar with using Android Studio to develop your Android apps in Kotlin.

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

Install the API Client

These instructions will install the PaLM Java API 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
    

Set 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 (e.g. “Palm App”).
    • Set a package name (e.g. 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.

Generate Text

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
}

Initialize the Text Service Client

Initialize a TextServiceClient by passing your API Key (supplied through the PALM_API_KEY environment variable) as a header to the TransportChannelProvider to be used by TextServiceSettings:

private fun initializeTextServiceClient(
    apiKey: String
): TextServiceClient {
    // (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" System.getenv("PALM_API_KEY"))))
        .build()

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

    // Initialize a TextServiceClient
    val textServiceClient = TextServiceClient.create(settings)

    return textServiceClient
}

Create a Text Prompt

You need to provide a TextPrompt to the API:

private fun createPrompt(
    textContent: String
): TextPrompt {
    val textPrompt = TextPrompt.newBuilder()
        .setText(textContent)
        .build()

    return textPrompt
}

Generate Text

Create a GenerateTextRequest

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

private fun createTextRequest(prompt: TextPrompt): GenerateTextRequest {
    return GenerateTextRequest.newBuilder()
        .setModel("models/text-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 texts to return
        .build()
}

Send the request

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

private fun generateText(
    request: GenerateTextRequest
) {
    viewModelScope.launch(Dispatchers.IO) {
        try {
            val response = client.generateText(request)
            val returnedText = response.candidatesList.last()
            // display the returned text in the UI
        } catch (e: Exception) {
            // There was an error
        }
    }
}

Put it all together

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

    class MainViewModel : ViewModel() {
      // Add the StateFlow
      private val _output = MutableStateFlow(value = "")
      val output: StateFlow<String>
          get() = _output
    
      // ... all the functions we created in previous steps
    
      private fun generateText(
          request: GenerateTextRequest
      ) {
          viewModelScope.launch(Dispatchers.IO) {
              try {
                  val response = client.generateText(request)
                  val returnedText = response.candidatesList.last()
                  // display the returned text in the UI
                  _output.update { returnedText.output }
              } catch (e: Exception) {
                  // There was an error, let's add a new text with the details
                  _output.update { "API Error: ${e.message}" }
              }
          }
      }
    
  2. Create the TextServiceClient, a TextPrompt and a GenerateTextRequest to send the 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: TextServiceClient
    
      init {
          // Initialize the Text Service Client
          client = initializeTextServiceClient(
              apiKey = "<insert your api key>"
          )
    
          // Create the text prompt
          val prompt = createPrompt("Repeat after me: one, two")
    
          // Send the first request
          val request = createTextRequest(prompt)
          generateText(request)
      }
    
      // other functions omitted for brevity
    }
    

A sample UI

We have created a sample UI to help you test the API. Open your 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

// ...

@Composable
fun SampleUi(
    viewModel: MainViewModel = viewModel()
) {
    val (inputText, setInputText) = remember { mutableStateOf("") }
    val textOutput: String by viewModel.output.collectAsState()
    Column(
        modifier = Modifier.padding(all = 16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        OutlinedTextField(
            modifier = Modifier.fillMaxWidth(),
            value = inputText,
            onValueChange = setInputText,
            label = { Text("Input:") }
        )
        Button(
            onClick = {
                viewModel.sendPrompt(inputText)
            },
            modifier = Modifier.padding(8.dp)
        ) {
            Text("Generate Text")
        }
        Card(
            modifier = Modifier.padding(vertical = 2.dp)
                .fillMaxWidth()
        ) {
            Column(
                modifier = Modifier.padding(8.dp)
            ) {
                Text(
                    modifier = Modifier.fillMaxWidth(),
                    text = textOutput,
                    style = MaterialTheme.typography.body1
                )
            }
        }
    }
}

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 "Generate Text" button, and a generated text.

App UI