How To Create A Chatbot In Android Studio
(Kotlin) How to Create a Chatbot
What's a chatbot? It's a Space application that communicates with a Space user in its own Chats channel. A minimum viable bot must:
-
Respond with a list of available commands when a user types
/
(slash) in the channel. -
Provide at least one command: After a user sends this command to the channel, the bot must do something and then respond with a message.
What will we do
Your first chatbot, of course! Without further ado, please welcome – the 'Remind me' bot!
All that our bot will be able to do is remind a user after a given amount of time. As simple as that: For example, a user sends the remind 60
command to the bot, and after 60 seconds, the bot sends a message with a reminder back. Also, our bot will have one more command that provides help
.

This tutorial will guide you through the entire chatbot creation process, but you can always download the resulting source code.
Chatbot-creator starter kit
What will we need along our journey?
![]() | JetBrains Intellij IDEA We'll write our bot in Kotlin. So, you can use any IDE of your choice, but, of course, this tutorial implies that you use Intellij IDEA. |
![]() | Ktor framework This is a framework that lets you easily create all types of connected applications, e.g., servers, clients, mobile, and browser-based apps. We'll get it as one of Gradle dependencies, so, no additional actions are required from your side. Of course, outside of this tutorial, you can create a Space bot using any web framework you like (e.g. Node.js, ASP.NET, and so on). |
![]() | Space SDK As you might know from the Applications topic, any application must communicate with Space using Space HTTP API. To ease the life of Space app developers, we provide Space SDK for Kotlin and .NET. The SDK contains the HTTP API client that lets you easily authenticate in and communicate with Space by using multiple high-level classes. As well as the Ktor framework, we'll get the SDK as a Gradle dependency. |
![]() | A tunnelling service Such a service exposes local servers to the public internet. It will let us run our chatbot locally and access it from Space via a public URL (we'll specify it as the chatbot's endpoint). For example, you can use ngrok, PageKyte, or another tunnelling service for this purpose. To start working with the tunneling service, you should download a service client: ngrok client, PageKyte client. For our purposes, the free plan for ngrok or PageKyte is enough. |
Step 1. Create a Ktor project
-
Open Intellij IDEA.
-
Start creating a new project with .
-
In the list of templates, select Ktor.
-
Specify a project Name and a Website name, select Gradle Groovy in Build System, and clear the Add sample code checkbox.
-
Click Next. We're not going to install any Ktor plugins, so, on the next page just click Finish.
-
That's it! Now, we have a blank Ktor project.
Step 2. Get Space SDK and other dependencies
Our chatbot requires the following libraries:
-
Space SDK for JVM
-
FasterXML/jackson: to work with JSON payloads.
-
Ktor HTTP client: a generic Ktor app doesn't reference libraries for HTTP client. We'll need a client to send requests to Space. We'll use the CIO Ktor HTTP client but you can use any other web engine for the HTTP client.
-
Open
build.gradle
and add:-
To the
repositories
section:maven { url 'https://kotlin.bintray.com/kotlinx' } maven { url "https://maven.pkg.jetbrains.space/public/p/space/maven" }
-
To the
dependencies
section, add dependencies to the required libraries:implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.13.0" implementation "org.jetbrains:space-sdk-jvm:81262-beta" implementation "io.ktor:ktor-client-core:$ktor_version" implementation "io.ktor:ktor-client-cio:$ktor_version"
Your
build.gradle
should look like follows:plugins { id 'application' id 'org.jetbrains.kotlin.jvm' version '1.5.31' } group "com" version "0.0.1" mainClassName = "com.ApplicationKt" repositories { mavenCentral() maven { url = "https://maven.pkg.jetbrains.space/public/p/space/maven" } maven { url 'https://kotlin.bintray.com/kotlinx' } } dependencies { implementation "io.ktor:ktor-server-core:$ktor_version" implementation "io.ktor:ktor-server-netty:$ktor_version" implementation "ch.qos.logback:logback-classic:$logback_version" testImplementation "io.ktor:ktor-server-tests:$ktor_version" testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.13.0' // we use version 81262, but when you read this tutorial // a newer version may be already available implementation "org.jetbrains:space-sdk-jvm:81262-beta" implementation "io.ktor:ktor-client-core:$ktor_version" implementation "io.ktor:ktor-client-cio:$ktor_version" }
-
-
In the Gradle window, click Reload All Gradle Projects to make Gradle download the required dependencies.
-
Done! Now, we have Space SDK in our project.
Step 3. Run the tunnelling service
Before we register our chatbot in Space, we have to get its publicly available URL. As your development environment is probably located behind NAT, the easiest way to get the URL is to use a tunnelling service. In our case, we'll use ngrok.
-
Download and unzip the ngrok client.
-
In terminal (on macOS or Linux) or in the command line (on Windows), open the ngrok directory.
-
By default, our Ktor project is configured to run the HTTP server on the port 8080 (you can check this in the
resources/application.conf
file). Run tunnelling for this port:./ngrok http 8080
-
The ngrok service will start. It will look similar to:
ngrok by @inconshreveable Session Status online Account john.doe@example.com (Plan: Free) Version 2.3.35 Region United States (us) Web Interface http://127.0.0.1:4040 Forwarding http://5a8dc7594bb0.ngrok.io -> http://localh Forwarding https://5a8dc7594bb0.ngrok.io -> http://local Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00
Here we are interested in the
Forwarding
line – it contains the public URL. ngrok redirects requests from this URL to our localhost using its tunnelling service. In the example above, the address ishttps://5a8dc7594bb0.ngrok.io
but in your case it'll be something else as ngrok generates these random URLs dynamically. -
Save the public URL, e.g. by copying it to the clipboard.
-
Great job! Now, we have a running tunnelling service and the public URL of our future chatbot.
Step 4. Register the chatbot in Space
Before we can start developing, we must get Space authentication credentials for our bot. Moreover, we also need a special verification token that will allow the bot to verify our Space instance. To get all of these, we must register our bot in Space.
-
Open your Space instance.
-
On the navigation bar, click
Administration and choose Applications.
-
Click New application.
-
Give the application a unique name, say,
remind-me-bot
and click Create. -
Open the Authorization tab. We will not change anything on this tab – as our bot is simple and doesn't get any data from Space.
We're on this tab just for you to note how important it is – if your app is supposed to access various Space modules, you should provide it corresponding permissions. To check what permissions are required for a certain HTTP API call, go to API Playground.
-
Open the Authentication tab. Our chatbot will authenticate on behalf of itself using the Client Credentials Flow: select the Client Credentials Flow checkbox and click Save. Also, we need to save application's Client ID and Client secret. Our chatbot will use them to get a Space access token via the client credentials flow.
-
When a user types anything in the chatbot channel, Space sends the user input to the application. So, our next step is to specify the URL of our application endpoint and choose how we will verify requests from Space.
Open the Endpoint tab.
In the Endpoint URI, specify the public URL generated by ngrok for our bot. Let's make this endpoint less generic and add an additional path to the URL, for example
api/myapp
. So, the final endpoint would behttps://{random_string_from_ngrok}.ngrok.io/api/myapp
By default, Space recommends using the Public key verification method. Let's leave the default and click Save.
-
Great job! Now, our bot is registered in Space, we have all required authentication data, and we're ready to start developing our bot.
Step 5. Create a Space client
All preparation steps are behind, let's do some coding! First, we should start with creating a Space client that will provide communication with our Space instance.
-
Add the
Client.kt
file to the project. -
Add the code to the
Client.kt
file:package com.remindme import io.ktor.client.* import space.jetbrains.api.runtime.SpaceHttpClient import space.jetbrains.api.runtime.helpers.verifyWithPublicKey import space.jetbrains.api.runtime.resources.chats import space.jetbrains.api.runtime.types.* import space.jetbrains.api.runtime.withServiceAccountTokenSource // url of your Space instance private const val url = "https://mycompany.jetbrains.space" // copy-paste client-id, and client-secret // your app got from Space private const val clientId = "" private const val clientSecret = "" // client for communication with Space // it uses the Client Credentials auth flow val spaceClient by lazy { SpaceHttpClient(HttpClient()) .withServiceAccountTokenSource( clientId = clientId, clientSecret = clientSecret, serverUrl = url ) } // verification of Space instance // gets a key from Space, uses it to generate message hash // and compares the generated hash to the hash in a message suspend fun verifyRequestWithPublicKey( body: String, signature: String, timestamp: String ): Boolean { return spaceClient.verifyWithPublicKey(body, timestamp.toLong(), signature) } // get user by Id and send message to the user // spaceClient gives you access to any Space endpoint // CallContext is shown as an error as we haven't created it yet suspend fun sendMessage(context: CallContext, message: ChatMessage) { spaceClient.chats.messages.sendMessage( MessageRecipient.Member(ProfileIdentifier.Id(context.userId)), message ) }
Notes:-
spaceClient
is our API client – an instance of theSpaceHttpClientWithCallContext
class.WithCallContext
stays here for a reason. Such a client allows working with the call context – additional data that clarifies who made the call, how it was made, and so on. We will use the context to get the ID of the user who made the request. -
SpaceHttpClient(HttpClient()).withServiceAccountTokenSource
defines how the client will authenticate in Space:withServiceAccountTokenSource
is used for the Client Credentials Flow. Other options would bewithCallContext
- for Refresh Token Flow andwithPermanentToken
for Permanent Token Authorization and other flows.HttpClient()
here is a Ktor HTTP client. -
clientId
andclientSecret
stand for the ID and secret we got during application registration. -
url
stands for the URL of your Space instance. -
verifyRequestWithPublicKey()
verifies the Space instance from which the client receives a request. It uses a public key from Space to calculate message hash and compare it to the hash sent in a message header. You can find more info about how it works here. -
sendMessage(context: CallContext, message: ChatMessage)
uses the client to send messages back to Space. Let's expand on that:-
CallContext
is our own class that will store call context. We'll create it in the next step. -
ChatMessage
is the API class that describes a Chat message. The thing is that messages are not just text. They can include complex formatting and even UI elements, like buttons. To simplify creating such messages, the API client provides a special DSL – Message Builder. We'll take a look at it in the next steps. -
spaceClient.chats.messages.sendMessage()
– look at how we refer to the Chats subsystem. The coolest thing about theSpaceHttpClient
class is that it lets you access any Space module directly. Thus, if you want to get a member profile, you can refer directly to Team Directory:spaceClient.teamDirectory.profiles.getProfile(ProfileIdentifier.Username("John.Doe"))
To see the list of modules that you can access via the client, open the API Playground. The top-level titles here are the modules that can be accessed:
-
-
-
Let's create the
CallContext
class: It will describe the context of the request send by a user. As our chatbot is very simple, we will require only the ID of the user who made a request – we will use this ID to send messages back to the user. In more complex cases, you can, for example, use the ID to get the full member info from Space and, based on that info, decide what chatbot functionality the user is allowed to use.Create the
CallContext.kt
file and add the following code:package com.remindme import space.jetbrains.api.runtime.types.* // here can be any context info, e.g. user info, payload, etc. class CallContext( val userId: String ) // get userId from the payload fun getCallContext(payload: ApplicationPayload): CallContext { val userId = when (payload) { is ListCommandsPayload -> payload.userId ?: error("no user for command") is MessageActionPayload -> payload.userId is MessagePayload -> payload.userId else -> error("unknown command") } return CallContext(userId) }
We'll talk about the types of payload in the next steps.
-
Done! Now, we have a client, so we can move on and create the first command for our chatbot.
Step 6. Create your first command
Let's start with something simple – the help
command that shows hints on how to use our chatbot.
-
Create the
CommandHelp.kt
file and add the following code:package com.remindme import space.jetbrains.api.runtime.helpers.message import space.jetbrains.api.runtime.types.* // command for showing chatbot help suspend fun commandHelp(context: CallContext) { sendMessage(context, helpMessage()) } // build the help message using special DSL fun helpMessage(): ChatMessage { return message { section { text("Soon the help will be shown here!") } } }
Notes:
-
commandHelp(context: CallContext)
is what our command actually does – it sends message with help back to the user. -
helpMessage(): ChatMessage
: The API client provides the specialmessage
DSL for building chat messages. As you can see, the help message consists of one section with text.
-
-
Nice! Now, we have a command that a user can actually try in action.
Step 7. Define an endpoint
The next step is to create the bot's endpoint in our code. After this step, we'll finally be able to send a command to our bot.
It is not a Ktor tutorial, so, we will not dig into Ktor specifics. By the way, if you're not familiar with Ktor, it's a good opportunity to get acquainted with this wonderful framework: just note how concise the Ktor syntax is.
-
Our chatbot is not only a client application, but it is also a server – it should listen for and handle the requests that come from Space. Server functionality in Ktor is handled by the routing feature. Let's use this feature to handle POST requests on the
/api/myapp
endpoint.Create the
Routes.kt
file and add the following code:package com.remindme import io.ktor.application.* import io.ktor.http.* import io.ktor.request.* import io.ktor.response.* import io.ktor.routing.* import space.jetbrains.api.runtime.types.* import space.jetbrains.api.runtime.helpers.command import space.jetbrains.api.runtime.helpers.readPayload fun Routing.api() { post("api/myapp") { // read request body val body = call.receiveText() // read headers required for Space verification val signature = call.request.header("X-Space-Public-Key-Signature") val timestamp = call.request.header("X-Space-Timestamp") // verify the request val verified = signature != null && timestamp != null && verifyRequestWithPublicKey(body, signature, timestamp) if (!verified) { call.respond(HttpStatusCode.Unauthorized) return@post } // read payload and get context (user id) val payload = readPayload(body) val context = getCallContext(payload) // analyze the message payload // MessagePayload = user sends a command // ListCommandsPayload = user types a slash or a char when (payload) { is MessagePayload -> { commandHelp(context) call.respond(HttpStatusCode.OK, "") } is ListCommandsPayload -> { } } } }
What is going on here:
-
readPayload(body: String)
is the SDK helper function that receives JSON data from Space and deserializes it intoApplicationPayload
. For example, in our case, the raw data from Space could look like follows:{ "className": "MessagePayload", "message": { "className": "MessageContext", "messageId": "JxT000JxT", "channelId": "31m0WE41iCBP", "body": { "className": "ChatMessage.Text", "text": "help" }, "createdTime": "2020-11-08T21:34:24.919Z" }, "accessToken": "", "verificationToken": "85e23ff", "userId": "1eAeu31CZA" }
-
The most interesting part is analyzing the type of payload. There are several payload types in the Space SDK, but, in this tutorial, we will focus only on two of them:
-
MessagePayload
: The standard payload that contains the command and command arguments. Actually, the bot receivesMessagePayload
when a user types something in the chat and presses Enter. -
ListCommandsPayload
: The bot receives this payload when a user hits the/
slash button. The bot must respond with a list of available commands.
-
-
Important: As you can see, in case of
MessagePayload
, the bot responds with the200 OK
HTTP status. If it doesn't, the user will get the500 Internal Server Error
.
-
-
Let's configure the routing feature. Open the
Routing.kt
file in thePlugins
directory and edit it as shown below:package com.remindme.plugins import com.remindme.api import io.ktor.application.* import io.ktor.routing.* fun Application.configureRouting() { routing { // call the api() function that handles our application endpoint api() } }
-
Make sure the
main()
application function starts the server with enabled routing:fun main() { embeddedServer(Netty, port = 8080) { configureRouting() }.start(wait = true) }
-
Done! Now, we have a working command, and we're ready to launch our bot for the first time!
Step 8. Run the bot
-
Open the
Application.kt
file. -
Start the application by clicking
Run
in the gutter next to themain
function inApplication.kt
: -
Open your Space instance and find the bot: press Ctrl+K and type its name.
-
If you look into the code, you can see that we don't anyhow analyze the commands sent by the user. On any request, we respond with the
helpMessage
. So, to test our bot, type anything in the chat. You should receive the help message: -
It's working! Now, let's add the rest of the bot features.
Step 9. Add support for slash commands
Let's make our bot fully functional:
-
In order to be considered a chatbot, an application must be able to respond with a list of available commands when a user hits
/
slash in the chat. In this case, the bot receives theListCommandsPayload
type of payload. -
As our bot is called the remind-me-bot, it needs some
remind
command that will start the timer and send the notification to the user once the timer is over.
-
Create the
Commands.kt
file:package com.remindme import space.jetbrains.api.runtime.types.* class Command( val name: String, val info: String, val run: suspend (context: CallContext, payload: MessagePayload) -> Unit ) { // part of the protocol - returns info about a command to the chat fun toCommand() = CommandDetail(name, info) } // list of available commands val commands = listOf( Command( "help", "Show this help", ) { context, payload -> commandHelp(context) }, Command( "remind", "Remind me in N seconds, e.g., to remind in 10 seconds, send 'remind 10' ", ) { context, payload -> commandRemind(context, payload) } ) // this is a response to the ListCommandsPayload // the bot must return a list of available commands // when a user types slash or a char fun commandListAllCommands(context: CallContext) = Commands( commands.map { it.toCommand() } )
Here:
-
Command
class describes the chatbot command: After a user sends the command namedname
to the chatbot, the bot must run the command'srun
function.Note that the command must implement the
toCommand
function that returnsCommandDetail
– command name and description. -
When a bot receives
ListCommandsPayload
, it will respond withcommandListAllCommands
that returns a map ofCommandDetail
.In fact, Space sends
ListCommandsPayload
every time a user presses a key. If it's the/
slash, Space will show the full list of commands. If it's some other key, Space will find and show only commands containing the key.
-
-
Create the
CommandRemind.kt
file:package com.remindme import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import space.jetbrains.api.runtime.helpers.message import space.jetbrains.api.runtime.helpers.commandArguments import space.jetbrains.api.runtime.types.* suspend fun commandRemind(context: CallContext, payload: MessagePayload) { val args = payload.commandArguments() val delayMs = args?.toLongOrNull()?.times(1000) runTimer(context, delayMs) } private suspend fun runTimer(context: CallContext, delayMs: Long?) { if (delayMs != null) { sendMessage(context, acceptRemindMessage(delayMs)) // we don't want to interrupt the thread, // so, we'll put our delay inside coroutineScope coroutineScope { delay(delayMs) sendMessage(context, remindMessage(delayMs)) } } else { sendMessage(context, helpMessage()) } } fun acceptRemindMessage(delayMs: Long): ChatMessage { return message { outline = MessageOutline( icon = ApiIcon("smile"), text = "I will remind you in ${delayMs / 1000} seconds" ) } } fun remindMessage(delayMs: Long): ChatMessage { return message { outline = MessageOutline( icon = ApiIcon("smile"), text = "Hey! ${delayMs / 1000} seconds are over!" ) } }
Here:
-
commandRemind
runs the timer for the specified amount of time.payload.commandArguments()
gets the arguments of theremind
command. So, when a user typesremind 10
, the arguments are10
. -
runTimer
sends theacceptRemindMessage
back to the user and once the timer is over, sendsremindMessage
. -
acceptRemindMessage
andremindMessage
use the message builder DSL to createChatMessage
. Both don't have body but only an outline.MessageOutline
is a small block that precedes the main message body. So, for a user, this will be shown as a tiny message written in a small font. TheApiIcon
returns an icon image that will be shown in front of the message.
-
-
Edit the
CommandHelp.kt
file:package com.remindme import space.jetbrains.api.runtime.helpers.message import space.jetbrains.api.runtime.types.* suspend fun commandHelp(context: CallContext) { sendMessage(context, helpMessage()) } fun helpMessage(): ChatMessage { return message { outline = MessageOutline( icon = ApiIcon("smile"), text = "Remind me bot help" ) section { header = "List of available commands" fields { commands.forEach { field(it.name, it.info) } } } } }
Here we've updated the
helpMessage
so that it now returns the list ofcommands
. -
Edit the
Routes.kt
file:package com.remindme import com.fasterxml.jackson.databind.ObjectMapper import io.ktor.application.* import io.ktor.http.* import io.ktor.request.* import io.ktor.response.* import io.ktor.routing.* import kotlinx.coroutines.launch import space.jetbrains.api.runtime.types.* import space.jetbrains.api.runtime.helpers.command import space.jetbrains.api.runtime.helpers.readPayload fun Routing.api() { post("api/myapp") { val body = call.receiveText() val signature = call.request.header("X-Space-Public-Key-Signature") val timestamp = call.request.header("X-Space-Timestamp") val verified = signature != null && timestamp != null && verifyRequestWithPublicKey(body, signature, timestamp) if (!verified) { call.respond(HttpStatusCode.Unauthorized) return@post } val payload = readPayload(body) val context = getCallContext(payload) // JSON serializer val jackson = ObjectMapper() when (payload) { is ListCommandsPayload -> { call.respondText( jackson.writeValueAsString(commandListAllCommands(context)), ContentType.Application.Json ) } is MessagePayload -> { val command = commands.find { it.name == payload.command() } if (command == null) { commandHelp(context) } else { launch { command.run(context, payload) } } call.respond(HttpStatusCode.OK, "") } } } }
What we changed here:
-
When the bot receives
ListCommandsPayload
(a user types/
slash), we now respond with a list of commands. Note that we usejackson
to convert the list to JSON. -
We analyze
MessagePayload
trying to find the requested command in the list of available commands.
-
-
Let's run the bot one more time and try all bot features:
-
Typing slash:
-
The
help
command: -
The
remind
command:
-
Great job! We have finished creating our simple bot. In the next tutorial we will learn how to add UI elements to chatbot messages.
Last modified: 26 October 2021
How To Create A Chatbot In Android Studio
Source: https://www.jetbrains.com/help/space/get-started-create-a-chatbot.html
Posted by: hamiltonwathre.blogspot.com
0 Response to "How To Create A Chatbot In Android Studio"
Post a Comment