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.
- Source: palm_api_android_quickstart.zip (tested on Android Studio Electric Eel 2022.1.1 Patch 1).
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.
- Download the google-cloud-ai-generativelanguage-v1beta3-java.tar.gz file.
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
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.
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" />
Open the
<project_root>/settings.gradle
file and addmavenLocal()
to therepositories
block withindependencyResolutionManagement
:pluginManagement { // ... } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() // Add this line mavenLocal() } }
Open your Gradle configuration file (
<project_root>/app/build.gradle
), be sure to exclude theINDEX.LIST
andDEPENDENCIES
files withinpackagingOptions
, and add the necessary libraries to thedependencies
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") }
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
In the MainViewModel class, create a
StateFlow
of messages that will be exposed to the UI, and update thegenerateMessage()
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() ) } } } } } }
Create the
DiscussServiceClient
, aMessagePrompt
and aGenerateMessageRequest
to send the first request in theMainViewModel
'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) }
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: