A ChatBot is a computer program that uses Artificial Intelligence (AI) and Natural Language Processing (NLP) to understand customers’ questions and give responses to them simulating human behavior. Building our own ChatBot from scratch requires us to train the Machine Learning (ML) model with rigorous amounts of text-based data, providing it with different contexts of real-world conversations. Now we don’t need to perform that meticulous task, as OpenAI already developed ChatGPT which was trained on billions of words of textual data which made it very powerful.
In this article, we will explain the total process of building a Mobile ChatBot Application using ChatGPT API and Flutter. We will be using Flutter Framework to build the application for Android. Flutter is an open-source framework developed and supported by Google to build and deploy hybrid applications quickly.
A sample video is given below to get an idea about what we are going to do in this article.
What is ChatGPT?
ChatGPT is a Large Language Model (LLM) developed by OpenAI that lets users enter prompts to receive textual information asked by the user. The language model can answer questions and assist you with tasks, such as composing emails, essays, and code. The “GPT” in ChatGPT refers to “Generative Pretrained Transformer”, in simple terms it uses Transformer architecture to respond to user’s prompts.
What is ChatGPT API?
API stands for Application Programming Interface. An API provides an interface to communicate with the resource/backend. Likewise, OpenAI had also created an API for developers to access this language model. The API is not totally free, OpenAI gives 5$ credit on signup which is more than enough for this project. We need to get an API key to proceed. Follow the below steps to continue!
Steps to Get API Key
To use the ChatGPT API, we need to create an account with OpenAI and generate an API key. If you have already used ChatGPT, you already have an account.
Note: You can skip this step if you already have an account.
- Head over to their official website and click on Signup.
- Signup using an email address or social accounts like Google, Microsoft, and Apple
- Fill up some basic details like Name, Birthdate, etc., and verify your mobile number.
Voila! you now have a brand new ChatGPT account to proceed with.
Generate API Key
Login to your account and head over to the OpenAI Platform.
Create an Application using Flutter
Tools Required
To build this app, you require the following tools on your machine :
- Visual Studio Code (Recommended IDE)
- Android Emulator / Original Device for testing.
- Flutter SDK
- Flutter Plugin for VS Code.
Create a New Project
Follow these steps to create a brand new project to make building the app.
- Create and name a folder of your choice.
- Open VS Code and open the newly created folder in it.
- Open the command palette by pressing CTRL + SHIFT + P and type Flutter.
- Choose Flutter: New Project from the listed options.
- Select Application from the drop down and it will open in the current folder you are in.
- Now name the app as per your choice, We are naming it with “chatbot” (Use lowercase convention).
Step-by-Step Implementation
Step 1:
After the initial setup of the project, you get a folder structure like this.
Open main.dart file from the lib folder.
Step 2:
Add the following required packages in the pubspec.yaml file just below the dependencies section which we are going to use in our app.
- http: (To communicate with API)
- chat_bubbles: (For displaying beautiful UI for individual messages)
Step 3:
Create a new file named chat_screen.dart in the lib folder.
Step 4:
Import the material file using the following statement in the newly created file.
import 'package:flutter/material.dart';
Import the below files for the working of the application.
import 'package:http/http.dart' as http;
import 'package:chat_bubbles/chat_bubbles.dart';
Step 5:
Open the AndroidManifest.xml file which is available under the android/app/src/main folder. Add the following line above the <application> tag to give the app internet permission to communicate with the language model.
<uses-permission android:name="android.permission.INTERNET" />
Step 6:
Create a Stateful Widget named ChatScreen to code the UI and Logic of the app. You can also use the below template to create the widget.
class ChatScreen extends StatefulWidget {
const ChatScreen({super.key});
@override
State<ChatScreen> createState() => _ChatScreenState();
} class _ChatScreenState extends State<ChatScreen> {
@override
Widget build(BuildContext context) {
return const Placeholder();
}
} |
Create a new file named message.dart in the lib folder. Below is the Dart code for it.
class Message {
bool isSender;
String msg;
Message( this .isSender, this .msg);
} |
Step 7:
Declare the following variables inside the ChatScreenState class i.e. above the build widget for future usage.
TextEditingController controller = TextEditingController(); ScrollController scrollController = ScrollController(); List<Message> msgs = []; bool isTyping = false ;
|
Usage of the above code
- controller is used to get the text that is being typed in the chatbox present at the bottom of the screen.
- scrollController is used to scroll to the bottom of the message list when a new message appears on the screen. This is used to give a animation and better user experience which is optional.
- msgs is a list of type Message that stores all the messages sent and received in the chat session.
- isTyping is a boolean that helps to display typing… animation/text on the screen to show the user that the chatbot is typing something to the message sent by the user.
Dart Code for UI
Step 8:
Now, we need a chatbox at the bottom of the screen to type the text and a send button for sending the typed prompt to the chatbot for getting a response from it. Below is the Dart UI code for the same.
Row( children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
width: double .infinity,
height: 40,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(10)),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: TextField(
controller: controller,
textCapitalization: TextCapitalization.sentences,
onSubmitted: (value) {
sendMsg();
},
textInputAction: TextInputAction.send,
showCursor: true ,
decoration: const InputDecoration(
border: InputBorder.none, hintText: "Enter text" ),
),
),
),
),
),
InkWell(
onTap: () {
sendMsg();
},
child: Container(
height: 40,
width: 40,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(30)),
child: const Icon(
Icons.send,
color: Colors.white,
),
),
),
const SizedBox(
width: 8,
)
],
),
|
Usage of the above code
- A Row is taken to arrange the chatbox and send button horizontally in a widget.
- A Container is taken and which is wrapped in an Expanded widget to make it expand within the row and give space for the user to type. It is also decorated by giving a grey colour and some rounded borders.
- A TextField is used inside the container, which helps the user to enter text and it’s stored/handled by the controller that we defined previously.
- An Icon widget is used which is wrapped inside another Container with a border having a send Icon wrapped inside InkWell widget to make it clickable, acting like a button.
- We can notice that if the text is submitted or send button is clicked, a function named sendMsg() is getting called which we will be implementing in the below steps.
Step 9:
Now we are remaining with adding chat bubbles (The individual rounded rectangular boxes that appear for each message). This can be achieved with the package that we imported previously – chat_bubbles. Below is the Dart UI code implementing it.
Expanded( child: ListView.builder(
controller: scrollController,
itemCount: msgs.length,
shrinkWrap: true ,
reverse: true ,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: isTyping && index == 0
? Column(
children: [
BubbleNormal(
text: msgs[0].msg,
isSender: true ,
color: Colors.blue.shade100,
),
const Padding(
padding: EdgeInsets.only(left: 16, top: 4),
child: Align(
alignment: Alignment.centerLeft,
child: Text( "Typing..." )),
)
],
)
: BubbleNormal(
text: msgs[index].msg,
isSender: msgs[index].isSender,
color: msgs[index].isSender
? Colors.blue.shade100
: Colors.grey.shade200,
));
}),
),
|
Usage of the above code
- A ListView.builder() is used to render all the chat bubbles i.e. individual messages on the screen.
-
It is given some parameters like
-> controller – For scroll controller
-> itemCount – Size of msgs List
-> shrinkWrap – Creates a scrollable, linear array of widgets that are created on demand.
-> reverse – To display the most recently added messages at the bottom of the list. - BubbleNormal widget is used to display individual messages with some colour.
- If isTyping is true and the index is 0, then typing… animation/text is shown, indicating to the user that the chatbot is typing something. Else, the message is shown based upon whether it is from the user or chatbot respectively.
Here is the final image of the UI that is coded up until now.
Dart Code for Functionality
Step 10:
The core function sendMsg() is implemented in this step. Add this function below the variables declared at the start. Below is the dart code for the same.
Note: Replace your-api-key with the API key that you generated and saved in past.
void sendMsg() async {
String text = controller.text;
String apiKey = "your-api-key" ;
controller.clear();
try {
if (text.isNotEmpty) {
setState(() {
msgs.insert(0, Message( true , text));
isTyping = true ;
});
scrollController.animateTo(0.0,
duration: const Duration(seconds: 1), curve: Curves.easeOut);
var response = await http.post(
headers: {
"Authorization" : "Bearer $apiKey" ,
"Content-Type" : "application/json"
},
body: jsonEncode({
"model" : "gpt-3.5-turbo" ,
"messages" : [
{ "role" : "user" , "content" : text}
]
}));
if (response.statusCode == 200) {
var json = jsonDecode(response.body);
setState(() {
isTyping = false ;
msgs.insert(
0,
Message(
false ,
json[ "choices" ][0][ "message" ][ "content" ]
.toString()
.trimLeft()));
});
scrollController.animateTo(0.0,
duration: const Duration(seconds: 1), curve: Curves.easeOut);
}
}
} on Exception {
ScaffoldMessenger.of(context).showSnackBar( const SnackBar(
content: Text( "Some error occurred, please try again!" )));
}
}
|
Usage of the above code
- The text entered by the user is obtained using the controller.
- If the text is not empty, a message is inserted into the msgs list and isTyping is made true, also the list is scrolled to the bottom to make the newly inserted message appear.
- The ChatGPT API is called on the “completions” end-point with header (apiKey, content-type) and body (text given by the user).
- Response is then parsed and added to the msgs list and displayed to the user.
Step 11:
The final step remaining is to update the main.dart file where the application starts running the code. Add the following dart code in the main.dart file.
import 'package:chatbot/chat_screen.dart' ;
import 'package:flutter/material.dart' ;
void main() async {
runApp( const MyApp());
} class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: ChatScreen(),
debugShowCheckedModeBanner: false ,
);
}
} |
Complete Dart Code (Chat Screen)
Here is the total Dart code representing the chat screen.
import 'dart:convert' ;
import 'package:chatbot/message.dart' ;
import 'package:chat_bubbles/bubbles/bubble_normal.dart' ;
import 'package:flutter/material.dart' ;
import 'package:http/http.dart' as http;
class ChatScreen extends StatefulWidget {
const ChatScreen({super.key});
@override
State<ChatScreen> createState() => _ChatScreenState();
} class _ChatScreenState extends State<ChatScreen> {
TextEditingController controller = TextEditingController();
ScrollController scrollController = ScrollController();
List<Message> msgs = [];
bool isTyping = false ;
void sendMsg() async {
String text = controller.text;
String apiKey = "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" ;
controller.clear();
try {
if (text.isNotEmpty) {
setState(() {
msgs.insert(0, Message( true , text));
isTyping = true ;
});
scrollController.animateTo(0.0,
duration: const Duration(seconds: 1), curve: Curves.easeOut);
var response = await http.post(
headers: {
"Authorization" : "Bearer $apiKey" ,
"Content-Type" : "application/json"
},
body: jsonEncode({
"model" : "gpt-3.5-turbo" ,
"messages" : [
{ "role" : "user" , "content" : text}
]
}));
if (response.statusCode == 200) {
var json = jsonDecode(response.body);
setState(() {
isTyping = false ;
msgs.insert(
0,
Message(
false ,
json[ "choices" ][0][ "message" ][ "content" ]
.toString()
.trimLeft()));
});
scrollController.animateTo(0.0,
duration: const Duration(seconds: 1), curve: Curves.easeOut);
}
}
} on Exception {
ScaffoldMessenger.of(context).showSnackBar( const SnackBar(
content: Text( "Some error occurred, please try again!" )));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text( "Chat Bot" ),
),
body: Column(
children: [
const SizedBox(
height: 8,
),
Expanded(
child: ListView.builder(
controller: scrollController,
itemCount: msgs.length,
shrinkWrap: true ,
reverse: true ,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: isTyping && index == 0
? Column(
children: [
BubbleNormal(
text: msgs[0].msg,
isSender: true ,
color: Colors.blue.shade100,
),
const Padding(
padding: EdgeInsets.only(left: 16, top: 4),
child: Align(
alignment: Alignment.centerLeft,
child: Text( "Typing..." )),
)
],
)
: BubbleNormal(
text: msgs[index].msg,
isSender: msgs[index].isSender,
color: msgs[index].isSender
? Colors.blue.shade100
: Colors.grey.shade200,
));
}),
),
Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
width: double .infinity,
height: 40,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(10)),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: TextField(
controller: controller,
textCapitalization: TextCapitalization.sentences,
onSubmitted: (value) {
sendMsg();
},
textInputAction: TextInputAction.send,
showCursor: true ,
decoration: const InputDecoration(
border: InputBorder.none, hintText: "Enter text" ),
),
),
),
),
),
InkWell(
onTap: () {
sendMsg();
},
child: Container(
height: 40,
width: 40,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(30)),
child: const Icon(
Icons.send,
color: Colors.white,
),
),
),
const SizedBox(
width: 8,
)
],
),
],
),
);
}
} |
Voila, your application is coded entirely and ready to be built, and run to get the output.
Step 12:
To run your app, select Run from the toolbar and click on Run Without Debugging or CTRL + F5 then select your preferred device to test the app. You can modify the code and update the UI as per your choice.
Output
Congratulations on building our chatbot. Here is the final output of the app.