In this tutorial, you will learn how to build a cross-platform AI chatbot mobile app using Flutter and the OpenAI Chat API.
We will create a clean chat interface, integrate with OpenAI’s GPT model, and handle responses in real-time.
By the end, you’ll have a working AI chatbot that runs on both Android and iOS. You can easily extend it with features like image generation, voice input, and conversation history.
Prerequisites
Before you begin, make sure you have:
-
Flutter installed (latest stable, tested with Flutter 3.22+)
-
Basic Flutter and Dart knowledge
-
An OpenAI account and API key (Sign up here)
-
A code editor (VS Code or Android Studio)
Step 1: Create a New Flutter Project
Open your terminal and run:
flutter create flutter_ai_chatbot
cd flutter_ai_chatbot
Step 2: Install Required Packages
We will use these packages:
-
http — for making API requests
-
flutter_dotenv — to store API keys securely in
.env
-
provider — for state management
Install them:
flutter pub add http flutter_dotenv provider
Step 3: Set Up Environment Variables
Create a .env
file in the project root:
OPENAI_API_KEY=sk-YOUR_KEY_HERE
OPENAI_MODEL=gpt-3.5-turbo
⚠ Important: Never commit your
.env
file to public repositories.
Step 4: Project Structure
Here’s the structure we will use:
lib/
main.dart
screens/
chat_screen.dart
providers/
chat_provider.dart
.env
Step 5: Configure main.dart
lib/main.dart
:
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:provider/provider.dart';
import 'providers/chat_provider.dart';
import 'screens/chat_screen.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await dotenv.load(fileName: ".env");
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => ChatProvider(),
child: MaterialApp(
title: 'Flutter AI Chatbot',
theme: ThemeData(primarySwatch: Colors.indigo),
home: const ChatScreen(),
),
);
}
}
Step 6: Create Chat Provider
lib/providers/chat_provider.dart
:
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_dotenv/flutter_dotenv.dart';
class ChatMessage {
final String role; // 'user' or 'assistant'
final String content;
ChatMessage({required this.role, required this.content});
}
class ChatProvider extends ChangeNotifier {
final List<ChatMessage> _messages = [];
bool _isLoading = false;
String? _error;
List<ChatMessage> get messages => List.unmodifiable(_messages);
bool get isLoading => _isLoading;
String? get error => _error;
final _apiKey = dotenv.env['OPENAI_API_KEY'];
final _model = dotenv.env['OPENAI_MODEL'] ?? 'gpt-3.5-turbo';
final _baseUrl = 'https://api.openai.com/v1/chat/completions';
void addUserMessage(String text) {
_messages.add(ChatMessage(role: 'user', content: text));
notifyListeners();
sendToOpenAI(text);
}
Future<void> sendToOpenAI(String userMessage) async {
if (_apiKey == null || _apiKey!.isEmpty) {
_error = 'OpenAI API key not set.';
notifyListeners();
return;
}
_isLoading = true;
_error = null;
notifyListeners();
final messagesPayload = [
{'role': 'system', 'content': 'You are a helpful assistant.'},
..._messages.map((m) => {'role': m.role, 'content': m.content}),
{'role': 'user', 'content': userMessage},
];
try {
final resp = await http.post(
Uri.parse(_baseUrl),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $_apiKey',
},
body: jsonEncode({
'model': _model,
'messages': messagesPayload,
}),
);
if (resp.statusCode == 200) {
final data = jsonDecode(resp.body);
final aiContent = data['choices'][0]['message']['content'] ?? '';
_messages.add(ChatMessage(role: 'assistant', content: aiContent.trim()));
} else {
_error = 'API Error (${resp.statusCode}): ${resp.body}';
}
} catch (e) {
_error = 'Request failed: $e';
} finally {
_isLoading = false;
notifyListeners();
}
}
}
Step 7: Build the Chat Screen
lib/screens/chat_screen.dart
:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/chat_provider.dart';
class ChatScreen extends StatefulWidget {
const ChatScreen({super.key});
@override
State<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final _controller = TextEditingController();
@override
Widget build(BuildContext context) {
final chat = context.watch<ChatProvider>();
return Scaffold(
appBar: AppBar(title: const Text('AI Chatbot')),
body: Column(
children: [
Expanded(
child: chat.messages.isEmpty
? const Center(child: Text('Say hi to the AI 👋'))
: ListView.builder(
reverse: true,
itemCount: chat.messages.length,
itemBuilder: (context, index) {
final msg = chat.messages[chat.messages.length - 1 - index];
final isUser = msg.role == 'user';
return Align(
alignment: isUser ? Alignment.centerRight : Alignment.centerLeft,
child: Container(
margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
padding: const EdgeInsets.all(12),
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.75),
decoration: BoxDecoration(
color: isUser ? Colors.indigo : Colors.grey.shade200,
borderRadius: BorderRadius.circular(12),
),
child: Text(
msg.content,
style: TextStyle(color: isUser ? Colors.white : Colors.black87),
),
),
);
},
),
),
if (chat.error != null)
Container(
color: Colors.red.shade100,
padding: const EdgeInsets.all(8),
child: Row(
children: [
const Icon(Icons.error, color: Colors.red),
const SizedBox(width: 8),
Expanded(child: Text(chat.error!)),
],
),
),
SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
textInputAction: TextInputAction.send,
onSubmitted: (_) => _send(context),
decoration: const InputDecoration(hintText: 'Type a message...'),
),
),
IconButton(
icon: chat.isLoading
? const CircularProgressIndicator()
: const Icon(Icons.send),
onPressed: chat.isLoading ? null : () => _send(context),
),
],
),
),
),
],
),
);
}
void _send(BuildContext context) {
final text = _controller.text.trim();
if (text.isEmpty) return;
context.read<ChatProvider>().addUserMessage(text);
_controller.clear();
}
}
Step 8: Run the App
Run:
flutter run
Type a message and see how the AI responds in real-time.
Step 9: Next Steps
Here are some ways to improve the app:
-
Streaming responses — Show AI typing as tokens arrive
-
Persistent chat history — Save chats using Hive or SQLite
-
Voice features — Add speech-to-text and text-to-speech
-
Image generation — Integrate OpenAI’s image API
-
Backend proxy — Avoid exposing API keys in mobile apps
Conclusion
You’ve successfully built an AI-powered chatbot with Flutter and OpenAI.
With just a few lines of code, you can connect a Flutter app to GPT models and deliver AI-driven experiences.
From here, you can expand this project into a personal assistant, customer service bot, or creative tool.
You can get the full source code on our GitHub.
That's just the basics. If you need more deep learning about Flutter, Dart, or related, you can take the following cheap course:
- Learn Flutter From Scratch
- Flutter, Beginner to Intermediate
- Flutter in 7 Days
- Learn Flutter from scratch
- Dart and Flutter: The Complete Developer's Guide
Thanks!