Build an AI Chatbot App with Flutter and OpenAI

by Didin J. on Aug 08, 2025 Build an AI Chatbot App with Flutter and OpenAI

Learn how to build an AI-powered chatbot app in Flutter using the OpenAI Chat API. Step-by-step guide with code examples, API integration, and a clean chat UI.

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:

Thanks!