Android and APIs

Hey Hack Schoolers! The quarter is quickly coming to a close and we know that you're excited about future hackathons. In order to prepare you for these hackathons, we will be mainly going over APIs (in particular, Chatbot's API) and we will teach you how to make your own chat bot!

Resources

Important: Unfortunately the first API we planned to use is down (in ChatBot-Starter and ChatBot-Solution), so we've included modified files for another API: ChatBot-Starter-2 and ChatBot-Solution-2. Please refer to these throughout the session.

What is an API?

The acronym API stands for Application Programming Interface. Examples of popular APIs that people use include Facebook, Google Maps, and Youtube. Here are two analogies that will help you understand what exactly an API is:

Analogy #1: Ordering Food Analogy

Suppose that you want to order food from a restaurant. To do that, you have to choose a specific dish that is on the menu and order it. Your order is then taken to the restaurant's kitchen. The chef will combine ingredients, heat it, and take on several processes to make your dish. The restaurant then sends your order back to you.

This is similar to how an API works. In order to receive what you want (the food), you have to place a request (place an order) in a specific way (as written in the menu). In this analogy, the menu is analogous to an interface. Then, your order is processed through a series of steps (your food is made in the kitchen) and your receive a response back (the restaurant serves you your dish).

Analogy #2: CS32

As you learned in CS32, abstract data types and object-oriented style programming allow objects to interact with each other through public methods. These objects contain private inner logic and implementations that are hidden from you, allowing you to focus on simply calling the methods instead of worrying about the details.

Let's get started with the activity

First, you will have to download the project by logging into your account on hackucla, going to the dashboard, selecting session 6, and clicking on the project link.

Note: In the project, you should be looking at ChatBot-Starter-2 and ChatBot-Solution-2. The reason why the originals don't work is because the API that we were originally using is currently down.

Setup: Dependencies

We need to do some basic setup in order to begin the project. Go to Gradle scripts > build.gradle (Module:app) and copy paste these three dependencies:

compile 'com.google.code.gson:gson:2.8.0'  
compile 'com.squareup.retrofit2:retrofit:2.2.0'  
compile 'com.squareup.retrofit2:converter-gson:2.2.0'  

Retrofit turns your HTTP API into something we can work with (a Java interface). GSON and Converter-GSON turn Java Objects into JSON representations. Basically, these allow you to make API requests easily.

Setup: Permissions

Any app that uses the Internet needs to specify that it will. We do this through setup permissions. Go to Manifests > AndroidManifest.xml and write the following:

<uses-permission android:name="android.permission.INTERNET" />  

Functionality Requirements

To give you a basic idea of what we have planned, our chatbot should:

  • be able to take input
  • have a button that sends an HTTP request
  • have a space to display the response
  • have graphical components such as a text multiline, a button, and a textview

The graphical components are already made in the starter pack, be sure to take a look at them before continuing on to the next step.

Interacting With Activity Within ChatBot.java

Now that we know what the user interface looks like, we're going to load references to them. Be sure to also call one of the functions we've provided a header for - initializeChatBot().

initializeChatBot();  
botChatText = (EditText) findViewById(R.id.chatText);  
botResponseText = (TextView) findViewById(R.id.chatResponse);  
submitButton = (Button) findViewById(R.id.submit);

We're going to implement an event listener for the submit button, so that we take the user input we receive and send it the API's chatbot as an HTTP request.

submitButton.setOnClickListener(  
    new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // TODO: send user input by calling sendChat();
                //       which we will implement at the very end.
                //       remember to come back here!
            }
        }
);

Overview of File Structure

Here is a broad overview of the methods below onCreate(), still within ChatBot.java, that we're going to implement to make our chat bot:

  • initializeChatbot()
  • sendChat(String chatMessage)

Within initalizeChatbot(), we're going to turn an interface that we will later define in ChatBotHTTP.java into a chatbot service that implements this interface.

Essentially, what this means is that within ChatBotHTTP.java, we will specify what we want the chatbot to do (what it will take in, and what it will send back), and Retrofit will do the rest for us.

Within sendChat(), we will send the user chat message to the chatbot API via the established service, receive the chatbot's response and return it. We will call this method and use the returned value to update our UI.

ChatBotHTTP

Within ChatBotHTTP.java

We only need one method in ChatBotHTTP and we need to specify that it's a GET request by adding the annotation @GET("chatbot"). The String passed in represents a route, which will automatically be appended to the base url we provided and defined in the starter code (you don't need to worry about that).

We will define two classes to handle the format of requests and responses we send and receive using the askChatBot() method, ChatBotResponse, and ChatBotRequest.

@GET("chatbot")
Call<ChatBotResponse> askChatBot(@QueryMap Map<String, String> request);  

Note that the parameter into this function actually isn't a ChatBotRequest object, like we might expect, but instead a map, which is will be returned by a function you will later implement within the chatbot request class - toMap(). This is because our GET request requires parameters that go into the URL, and can't be turned into JSON. The @QueryMap annotation is used to construct the key-value pairs represented by the Map we pass in.

The type we receive back is a ChatBotResponse object.

ChatBotResponse

Within ChatBotResponse.java

This class is relatively straightforward; all we do is define data members and a constructor that will initialize them.

To determine what data members we will need and consequently determine the function header for our constructor, we should look at the documentation for our API to see the format of the response we get back.

Here is the documentation link.

This screenshot shows us the three data members we need:

From this information, we can see that the constructor should like this:

public ChatBotResponse(String convo_id, String usersay, String botsay) {  
        this.convo_id = convo_id;
        this.usersay = usersay;
        this.botsay = botsay;
}

ChatBotRequest

Within ChatBotRequest.java

In the same way, we have to look at the documentation in order to see how to format our GET request.

We see that the four data members we need are bot_id, say, convo_id, and format.

public ChatBotRequest(int bot_id, String format, String convo_id, String say) {  
        this.bot_id = bot_id;
        this.format = format;
        this.convo_id = convo_id;
        this.say = say;
}

After creating our data members and constructor, we need to format it as a Map so that it can be properly sent with the GET request as parameters, as we mentioned before (which is why we use the @QueryMap annotation).

public Map<String, String> toMap() {  
        Map<String, String> map = new HashMap<String,String>();
        map.put("bot_id", (new Integer(this.bot_id)).toString());
        map.put("format", this.format);
        map.put("convo_id", this.convo_id);
        map.put("say", this.say);

        return map;
}

initalizeChatBot()

Within ChatBot.java

Now, we have to pass in the interface we created in ChatBotHTTP.java to Retrofit in order to create a chatbot service.

Retrofit retrofit = new Retrofit.Builder()  
                .baseUrl(API_BASEURL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

//saving the service into a global variable
chatBotService = retrofit.create(ChatBotHTTP.class);  

We also need to create a conversation ID according to the API documentation so that we can keep track of the conversation. We do this by generating a random 32-character string.

chatBotId = (new BigInteger(160, new Random())).toString(32);  

sendChat()

Within ChatBot.java

This is our last function! Here is where we set up the actual API request.

First, we have to make basic boundary checks to ensure that we have initialized the chatbot and that we're not sending an empty message.

if (chatBotService == null ||  
    chatBotId == null ||
    chatMessage.trim().length() == 0)
    return;

Then, we have to prepare to make the GET request, passing in the user's message in order to receive a response to display. This request will be asynchronous. What this means is that we make the request in the background while other things are happening to prevent our app from freezing up. While we're making the request, we have to set text to let the user know that their input is being processed.

botResponseText.setText("Thinking...");  

Now, we have to actually set up the asynchronous request. We have to handle both success and failure events with onResponse and onFailure method overrides.

Notice that within the onResponse function, we call setText() on one of the UI elements we defined before, botResponseText. We pass in response.body(), which is the response we received back from the API request, and .botsay is a property of the response that contains the bot's reply to the user's input message (which is what we're interested in).

call.enqueue(new Callback<ChatBotResponse>() {  
   @Override
   public void onResponse(Call<ChatBotInstance> call,   Response<ChatBotInstance> response) {
     botResponseText.setText(response.body().botsay);
}

   @Override
   public void onFailure(Call<ChatBotInstance> call, Throwable t) {
     botResponseText.setText("Whoops! There was an error: " + t.toString());
   }
});

For our final step, we have to call this function, sendChat() within our Button event listener from the very beginning of this tutorial. Within the event listener, we can retrieve the user's input message and then pass it to the function:

sendChat(botChatText.getText().toString());  

And we're done! If you're confused by anything, check out the ChatBot-Solution-2 folder within the github repository we provided in the beginning. Happy Hacking!