Create a Conversational Interface for Android With Dialogflow

What You'll Be Creating

The rise of artificial intelligence is triggering a paradigm shift in the field of user interface development. Thanks to the proliferation of intelligent, voice-activated assistants such as Google Home, Siri, and Alexa, users are beginning to feel that pressing numerous buttons on a screen or manually filling out forms is not only inefficient and slow, but also old-fashioned.

Fortunately, there are many cloud-based services available today that make it easy for developers to add conversational user interfaces to their apps. Google's Dialogflow Standard Edition is one such service. It's free, very powerful, multilingual, and comes with a large number of well-designed templates.

In this tutorial, I'll show you how to create a simple text-based conversational user interface for Android using Dialogflow.

Prerequisites

Before you proceed, make sure you have access to:

  • the latest version of Android Studio
  • a device or emulator running Android 5.0 or higher

1. Creating an Agent

While using Dialogflow, you'll always be working with an agent, a natural language understanding system trained to handle a specific set of user inputs.

To create your first agent, use a Google account to log in to the Dialogflow console and press the Create agent button.

In the form that pops up, give a sensible name to the agent and press the Create button.

After a few seconds, you'll have a brand new agent.

2. Creating an Intent

While visual interfaces have buttons users can press to express their intentions, conversational interfaces have intents. As such, in a well-designed conversational interface, everything a user can say is mapped to an intent. An agent's job is only to accurately determine which intent needs to be activated when a user utters or types in a phrase or sentence.

To keep things simple, let's create just one intent for our agent: an intent named WEIGHT , which will let users convert weights in kilograms to pounds and vice versa.

To create the intent, press the Create intent button in the console.

On the next screen, type in the intent's name and press the Add training phrases button. You'll now be able to provide multiple phrases the agent can use to train itself. For example, the sentence "what is 32 kilograms in pounds" would be a good training phrase for the WEIGHT intent.

After you type in the sentence and hit the Enter key, you'll see that Dialogflow correctly guesses that the phrase "32 kilograms" is variable. It'll also automatically create a programmatically accessible parameter named unit-weight for it and set its type to  @sys.unit-weight .

Similarly, it guesses that the word "pounds" too is variable and creates a parameter for it named unit-weight-name , whose type is @sys.unit-weight-name .

I suggest you type in a few more similar training phrases, always making sure that the unit-weight and unit-weight-name parameters are resolved to the correct values.

Next, press the Add responses button to type in a few generic responses. Do understand that these will be shown to the user verbatim.

When you are satisfied with the training phrases and responses you've provided, go ahead and press the Save button to save the intent and start the training process, which will usually run for less than a minute.

As I said earlier, a good conversational interface must be able to handle everything the user says. That means that our agent must also be able to map pleasantries and sentences it doesn't understand to valid intents. Because this is a very common requirement, Dialogflow automatically generates such intents for us, aptly named Default Welcome Intent and Default Fallback Intent . While the latter doesn't need any changes, the former does.

The Default Welcome Intent doesn't have any training phrases, so you must provide a few. Additionally, we won't be working with events in this tutorial, so you can remove the  Welcome event associated with it. 

Press the Save button after making the changes.

3. Enabling Small Talk

Most users are unlikely to limit themselves to the WEIGHT intent you created. Although the fallback intent will be able to handle all invalid queries, it's always a good idea to train the agent so that it can engage in small talk. Doing so will make it seem more human.

In the Dialogflow console, adding small talk abilities to the agent is very easy. All you need to do is open the Small talk tab and press the Enable button.

At this point, the agent will be able to generate default responses for a lot of common questions. Optionally, you can customize those responses to give it a unique personality. For now, I suggest you answer a few questions in the About agent section and press the Save button.

4. Getting an Access Token

Your Android app will need a client access token while communicating with the Dialogflow agent. To get it, click on the gear icon beside the agent's name and open the General tab. On scrolling down to the API keys section, you will be able to see the token. Note it down so you can use it later.

5. Adding Project Dependencies

We'll be using the Fuel networking library while interacting with Dialogflow's web service, so add the following implementation dependency in the app module's build.gradle file:

implementation 'com.github.kittinunf.fuel:fuel-android:1.12.1'

Dialogflow can handle both text and audio. In this tutorial, however, we'll be working only with text. Consequently, our app is going to have a chat app-like user interface. So add the ChatMessageView library as another dependency.

implementation 'com.github.bassaer:chatmessageview:1.10.0'

Finally, make sure your app can connect to the Internet by requesting the following permission in the AndroidManifest.xml file:

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

6. Defining the Layout

The ChatMessageView library's ChatView widget offers a fully fledged chat user interface capable of both displaying chat messages and accepting user input. By using it in our layout, we can save a lot of time and effort. So place the widget inside a FrameLayout widget and add it to your layout XML file.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.tutsplus.dialogflowtutorial.MainActivity">

    <com.github.bassaer.chatmessageview.view.ChatView
        android:id="@+id/my_chat_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</FrameLayout>

If you have Kotlin Android Extensions enabled in your project, a reference to the widget will be available as an extension property inside your activity.

I suggest you run your app now to take a look at the layout you just created.

7. Configuring Fuel

You can make your networking code a lot more concise by configuring the Fuel client specifically to use the Dialogflow web service. Before you do so, however, add the client access token you obtained earlier as a compile-time constant to your activity.

companion object {
    private const val ACCESS_TOKEN = "1234567890abcdef"
}

All HTTP requests you make to the Dialogflow web service must have an Authorization header based on the token. To avoid manually creating the header every time you make a request, use the baseHeaders property of the  FuelManager class.

FuelManager.instance.baseHeaders = mapOf(
    "Authorization" to "Bearer $ACCESS_TOKEN"
)

Next, set the basePath property of the FuelManager class to the base URL of the Dialogflow web service.

FuelManager.instance.basePath = 
                "https://api.dialogflow.com/v1/"

Lastly, all your HTTP requests must always have the following configuration parameters: a v parameter specifying the protocol version you want to use, a lang parameter specifying the language you want the agent's replies to be in, and a sessionId parameter whose value can be any random string.

The following code shows you how to use the baseParams property to set all the parameters:

FuelManager.instance.baseParams = listOf(
    "v" to "20170712",                  // latest protocol
    "sessionId" to UUID.randomUUID(),   // random ID
    "lang" to "en"                      // English language
)

8. Configuring the Chat Interface

The ChatView widget needs two ChatUser objects: one for the user and one for the agent. These objects are meant for storing details such as the names and profile pictures that should be displayed along with the chat messages. Additionally, each ChatUser object must have a unique ID associated with it.

The following code shows you how to create the objects:

val human = ChatUser(
        1,
        "You",
        BitmapFactory.decodeResource(resources,
                R.drawable.ic_account_circle)
)

val agent = ChatUser(
        2,
        "Agent",
        BitmapFactory.decodeResource(resources,
                R.drawable.ic_account_circle)
)

Note that the code uses a built-in resource named ic_account_circle as the avatar for both the objects. Feel free to use any other drawable resource if you want to.

9. Sending and Receiving Messages

Whenever users press the send button of the ChatView widget, you must create  Message objects based on the text they typed in. To do so, you can use the  Message.Builder class. While creating the object, you'll have to make sure it belongs to the human user by calling the setUser() method.

Once the Message object is ready, you can pass it to the send() method of the  ChatView widget to render it. The following code shows you how to do so inside the  setOnClickSendButtonListener() method of the ChatView widget.

my_chat_view.setOnClickSendButtonListener(
    View.OnClickListener {
        my_chat_view.send(Message.Builder()
                .setUser(human)
                .setText(my_chat_view.inputText)
                .build()
        )

        // More code here
    }
)

To actually send the user's message to your agent, you must now make an HTTP GET request to the /query endpoint of the Dialogflow web service. As an input, it expects a  query parameter, whose value can be any phrase or sentence the user typed in.

As an HTTP response, you'll get a JSON document whose result/fulfillment/speech value contains the agent's reply.

Fuel.get("/query",
        listOf("query" to my_chat_view.inputText))
        .responseJson { _, _, result ->
    val reply = result.get().obj()
                    .getJSONObject("result")
                    .getJSONObject("fulfillment")
                    .getString("speech")

    // More code here
}

To render the reply inside the ChatView widget, you must again build another  Message object. This time, however, its owner must be the agent. Additionally, to render the message on the right-hand side, you must pass true to its  setRight() method.

my_chat_view.send(Message.Builder()
        .setRight(true)
        .setUser(agent)
        .setText(reply)
        .build()
)

If you run the app now, you should be able to chat with the agent.

If you ask the app to convert a weight in kilograms to pounds, however, it'll only give a generic reply. To be able to actually perform the conversion, you must first determine if the WEIGHT intent was triggered. To do so, you can check the value of the  result/metadata/intentName key.

val intent:String? = result.get().obj()
                    .getJSONObject("result")
                    .optJSONObject("metadata")
                    .optString("intentName")

if(intent!! == "WEIGHT") {
    // More code here
}

Once you are sure that the WEIGHT intent was triggered, you can determine the values of the  unit-weight-name and unit-weight parameters, which will be present inside the  result/parameters object.

// Convert to what
val unitWeightName = result.get().obj()
        .getJSONObject("result")
        .getJSONObject("parameters")
        .getString("unit-weight-name")

// The weight that needs to be converted
val unitWeight = result.get().obj()
        .getJSONObject("result")
        .getJSONObject("parameters")
        .getJSONObject("unit-weight")
        .getDouble("amount")

With the above values, all it takes is simple math and an if-else statement to perform the conversion. To render the result, you will need another Message object. Its owner too must be the agent.

// Perform conversion
val result = if(unitWeightName == "lb") {
    unitWeight * 2.20462
} else {
    unitWeight / 2.20462
}

// Render result
my_chat_view.send(Message.Builder()
    .setRight(true)
    .setUser(agent)
    .setText("That's ${"%.2f".format(result)} $unitWeightName")
    .build()
)

Our app is ready. You should be able to run it now to see that it performs all the conversions correctly.

Conclusion

You now know how to create a friendly and useful agent using Google Dialogflow. In this tutorial, you also learned how to create an appealing interface that people can use while communicating with the agent.

To learn more about Dialogflow, refer to its official documentation .

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章