Adding AI-Generated Text to Your Android App with Jetpack Compose and GPT

This tutorial shows how to build a GPT client for Android using Jetpack Compose in Kotlin. It covers sending a request to the OpenAI GPT API and decoding the JSON response to display the generated text.

Note: this tutorial focuses on the logic for sending and receiving requests to /from the OpenAI GPT API; it does not cover all UI details.

For the full code, including the UI implementation, see my GitHub repo with working iOS and Android samples.

What is GPT?

GPT (Generative Pre-trained Transformer) is a machine learning model that generates natural language text. It is pre-trained on large amounts of text and can be fine-tuned for specific tasks, such as producing text in response to a prompt.

What will we build?

We will build an app that:

  • Accepts user input from a TextField
  • Sends the input to the GPT API
  • Receives and decodes the JSON response
  • Displays the answer as animated Text

The sendRequest function handles communication with the OpenAI GPT API. It encodes the user’s question as a JSON payload and sends a POST request. If the API responds with status code 200, the function extracts the generated text from the JSON response and displays it in a Text view. If the API responds with an error, the function displays an error message.

Here is an animated screenshot of the final app:

2023-03-07 gpt.gif

Sending a Request to the GPT API

Below is the code needed to send a request to the GPT API. A step-by-step explanation follows.

data class GptRequest(
  val prompt: String,
  val max_tokens: Int,
  val model: String
)
 
data class GptResponse(
  val choices: List<Choice>
)
 
data class Choice(
  val text: String
)
 
interface GptApi {
  @Headers(
    "Content-Type: application/json",
    "Authorization: Bearer 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"
  )
  @POST("/v1/completions")
  fun getCompletion(
    @Body requestBody: GptRequest
  ): Call<GptResponse>
}
 
class ContentViewModel: ViewModel() {
 
  var answer by mutableStateOf("")
    private set
 
  var isLoading by mutableStateOf(false)
    private set
 
  val retrofit = Retrofit.Builder()
   .baseUrl("https://api.openai.com")
   .addConverterFactory(GsonConverterFactory.create())
   .build()
 
  val api = retrofit.create(GptApi::class.java)
 
  fun sendRequest(question: String) {
    val request = GptRequest(
      prompt = question,
      max_tokens = 100,
      model = "text-davinci-003"
    )
 
    viewModelScope.launch(Dispatchers.IO) {
      isLoading = true
      val call = api.getCompletion(request)
      val response = call.execute()
      isLoading = false
 
      if (response.isSuccessful) {
        val choice = response.body()?.choices?.get(0)
        viewModelScope.launch(Dispatchers.Main) {
          choice?.text?.let {
            answer = it
          }
        }
      } else {
        viewModelScope.launch(Dispatchers.Main) {
          answer = "Error: ${response.code()} - ${response.message()}"
        }
      }
    }
  }
}

The source code above enables the app to communicate with the OpenAI GPT API. Below is a concise explanation of the main parts:

  • data class GptRequest represents the JSON payload sent to the OpenAI API to request generated text.
  • data class GptResponse represents the JSON response returned by the OpenAI API and contains the generated text.
  • data class Choice is a nested data class inside GptResponse that contains the generated text.
  • interface GptApi defines the REST API used to communicate with the OpenAI API. The getCompletion function sends a POST request to the v1/completions endpoint with a Content-Type: application/json header and an Authorization header containing the API key.
  • class ContentViewModel is a ViewModel that handles the app’s data and logic. It includes a sendRequest function that takes a user’s question, sends it to the OpenAI API, and sets the answer variable to the generated text.
  • var answer by mutableStateOf("") is a reactive variable that holds the generated text and can be observed by the UI for display.
  • var isLoading by mutableStateOf(false) is a reactive variable that indicates whether a request is in progress and can be observed by the UI to show a loading spinner.
  • val retrofit = Retrofit.Builder() creates a Retrofit instance used to communicate with the OpenAI API.
  • val api = retrofit.create(GptApi::class.java) creates an instance of the GptApi interface using the Retrofit instance.
  • fun sendRequest(question: String) constructs a GptRequest, sends it via api.getCompletion(request), and sets answer to the generated text if the response is successful; otherwise it displays an error message.
  • Dispatchers.IO and Dispatchers.Main are used to run network requests and UI updates on appropriate threads.

Note: The Authorization header in the code above contains an API key specific to the developer (me). Replace it with your own API key—using the provided key will result in an error because it was invalidated before this tutorial’s release.

To obtain an OpenAI API key, create an account at openai.com, go to the API section, and follow the instructions to generate a key. Keep your API key secure and do not share it.

Conclusion

In this tutorial we built an Android app that accepts user input, sends it to the OpenAI GPT API, and displays the generated text.

I hope you found this tutorial informative and that it inspired you to create your own project using the GPT API. If you have questions or feedback, feel free to leave a comment.

The full code is available in my repo with working samples for iOS and Android: https://github.com/twissmueller/mobile-snippets

Thank you for reading!