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:
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.
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.
Powered by WPeMatico