Artificial Intelligence has transformed mobile software development. Modern Flutter applications go beyond static screens—they offer intelligent chat assistance, ambient recommendations, automated text parsing, and multimodal vision analysis.
In this tutorial, we will build an AI-powered Flutter application using the official Google Gemini SDK, real-time response streaming, structured JSON responses, and responsive UI components.
1. Prerequisites & Project Setup
Start by creating a new Flutter project and adding the official Google AI SDK:
flutter create ai_flutter_demo
cd ai_flutter_demo
flutter pub add google_generative_ai providerPubspec Dependencies (pubspec.yaml)
dependencies:
flutter:
sdk: flutter
google_generative_ai: ^0.4.0
provider: ^6.1.22. Configuring the Gemini Service
We create a dedicated service class to handle initialization, prompt generation, and error handling.
import 'package:google_generative_ai/google_generative_ai.dart';
class GeminiRepository {
late final GenerativeModel _textModel;
late final GenerativeModel _visionModel;
GeminiRepository({required String apiKey}) {
_textModel = GenerativeModel(
model: 'gemini-1.5-flash',
apiKey: apiKey,
);
_visionModel = GenerativeModel(
model: 'gemini-1.5-pro',
apiKey: apiKey,
);
}
/// Streaming token responses for instant user feedback
Stream<String> streamChatResponse(String userPrompt) async* {
try {
final content = [Content.text(userPrompt)];
final responseStream = _textModel.generateContentStream(content);
await for (final chunk in responseStream) {
if (chunk.text != null && chunk.text!.isNotEmpty) {
yield chunk.text!;
}
}
} catch (e) {
yield 'Error generating response: ${e.toString()}';
}
}
}3. Building a Real-Time Streaming AI Chat UI
Users expect instant, typewriter-style token streaming when interacting with AI chat interfaces. Here is how to implement a clean streaming chat widget in Flutter.
import 'package:flutter/material.dart';
class AiChatScreen extends StatefulWidget {
final GeminiRepository repository;
const AiChatScreen({super.key, required this.repository});
@override
State<AiChatScreen> createState() => _AiChatScreenState();
}
class _AiChatScreenState extends State<AiChatScreen> {
final TextEditingController _controller = TextEditingController();
final List<ChatMessage> _messages = [];
bool _isGenerating = false;
void _sendMessage() async {
final text = _controller.text.trim();
if (text.isEmpty || _isGenerating) return;
_controller.clear();
setState(() {
_messages.add(ChatMessage(sender: 'User', text: text));
_messages.add(ChatMessage(sender: 'AI', text: '...'));
_isGenerating = true;
});
final aiMessageIndex = _messages.length - 1;
StringBuffer fullResponse = StringBuffer();
widget.repository.streamChatResponse(text).listen(
(chunk) {
fullResponse.write(chunk);
setState(() {
_messages[aiMessageIndex] = ChatMessage(
sender: 'AI',
text: fullResponse.toString(),
);
});
},
onDone: () {
setState(() {
_isGenerating = false;
});
},
onError: (err) {
setState(() {
_messages[aiMessageIndex] = ChatMessage(
sender: 'AI',
text: 'An error occurred. Please try again.',
);
_isGenerating = false;
});
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Gemini AI Assistant')),
body: Column(
children: [
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: _messages.length,
itemBuilder: (context, index) {
final msg = _messages[index];
final isUser = msg.sender == 'User';
return Align(
alignment: isUser ? Alignment.centerRight : Alignment.centerLeft,
child: Container(
margin: const EdgeInsets.symmetric(vertical: 4.0),
padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
color: isUser ? Colors.blueAccent : Colors.grey[800],
borderRadius: BorderRadius.circular(12.0),
),
child: Text(
msg.text,
style: const TextStyle(color: Colors.white),
),
),
);
},
),
),
Container(
padding: const EdgeInsets.all(8.0),
color: Theme.of(context).cardColor,
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Ask Gemini anything...',
border: InputBorder.none,
),
onSubmitted: (_) => _sendMessage(),
),
),
IconButton(
icon: _isGenerating
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.send),
onPressed: _sendMessage,
),
],
),
),
],
),
);
}
}
class ChatMessage {
final String sender;
final String text;
ChatMessage({required this.sender, required this.text});
}4. Multimodal AI Analysis (Images & Vision)
Gemini 1.5 Pro enables image analysis directly within mobile apps. Users can upload images (e.g., receipts, plant identification, document OCR) for real-time analysis.
import 'dart:typed_data';
Future<String?> analyzeImage(Uint8List imageBytes, String prompt) async {
final model = GenerativeModel(
model: 'gemini-1.5-flash',
apiKey: 'YOUR_API_KEY',
);
final content = [
Content.multi([
TextPart(prompt),
DataPart('image/jpeg', imageBytes),
])
];
final response = await model.generateContent(content);
return response.text;
}5. Security & Production Considerations
When deploying AI-powered applications to production:
- Never Hardcode API Keys: Proxy API requests through a secure backend (Firebase Cloud Functions, Supabase Edge Functions, or Next.js API Routes).
- Rate Limiting & Cost Control: Implement token rate-limiting to prevent billing spikes.
- Graceful Fallbacks: Provide clear UI offline indicators when users lose network connectivity.