AI Integration in Flutter
Hands-on guide to building real AI-powered features in Flutter applications.
📋 Prerequisites
Read Introduction to AI in Flutter first. You should have a working Flutter development environment.
Text Recognition (OCR)
Optical Character Recognition lets you extract text from images — perfect for scanning documents, receipts, or handwritten notes.
Setup
pubspec.yaml
dependencies:
google_mlkit_text_recognition: ^0.13.0
image_picker: ^1.0.7
Implementation
ocr_service.dart
import 'dart:io';
import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';
class OcrService {
final _textRecognizer = TextRecognizer(
script: TextRecognitionScript.latin,
);
Future<String> recognizeText(File imageFile) async {
final inputImage = InputImage.fromFile(imageFile);
final recognized = await _textRecognizer.processImage(inputImage);
if (recognized.text.isEmpty) {
return 'No text found in image.';
}
// Process individual blocks
for (final block in recognized.blocks) {
print('Block: ${block.text}');
for (final line in block.lines) {
print(' Line: ${line.text}');
}
}
return recognized.text;
}
void dispose() => _textRecognizer.close();
}
Widget Usage
scanner_screen.dart
class ScannerScreen extends StatefulWidget {
const ScannerScreen({super.key});
@override
State<ScannerScreen> createState() => _ScannerScreenState();
}
class _ScannerScreenState extends State<ScannerScreen> {
final _ocrService = OcrService();
String _result = '';
bool _isProcessing = false;
Future<void> _pickAndScan() async {
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.camera);
if (image == null) return;
setState(() => _isProcessing = true);
final text = await _ocrService.recognizeText(File(image.path));
setState(() {
_result = text;
_isProcessing = false;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Text Scanner')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
ElevatedButton.icon(
onPressed: _isProcessing ? null : _pickAndScan,
icon: const Icon(Icons.camera_alt),
label: Text(_isProcessing ? 'Scanning...' : 'Scan Text'),
),
const SizedBox(height: 16),
Expanded(
child: SelectableText(_result, style: const TextStyle(fontSize: 16)),
),
],
),
),
);
}
@override
void dispose() {
_ocrService.dispose();
super.dispose();
}
}
Barcode Scanning
Scan QR codes, EAN-13, Code 128, and 20+ other barcode formats.
barcode_service.dart
import 'dart:io';
import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart';
class BarcodeService {
final _barcodeScanner = BarcodeScanner(
formats: [BarcodeFormat.qrCode, BarcodeFormat.ean13],
);
Future<Barcode?$> scanBarcode(File imageFile) async {
final inputImage = InputImage.fromFile(imageFile);
final barcodes = await _barcodeScanner.processImage(inputImage);
if (barcodes.isEmpty) return null;
final barcode = barcodes.first;
print('Type: ${barcode.type}');
print('Value: ${barcode.rawValue}');
return barcode;
}
void dispose() => _barcodeScanner.close();
}
Image Labeling
Identify objects, scenes, and concepts in images automatically.
label_service.dart
import 'dart:io';
import 'package:google_mlkit_image_labeling/google_mlkit_image_labeling.dart';
class ImageLabelService {
final _labeler = ImageLabeler(
options: ImageLabelerOptions(confidenceThreshold: 0.5),
);
Future<List<ImageLabel>> labelImage(File imageFile) async {
final inputImage = InputImage.fromFile(imageFile);
final labels = await _labeler.processImage(inputImage);
for (final label in labels) {
print('${label.label} (${(label.confidence * 100).toStringAsFixed(1)}%)');
}
return labels;
}
void dispose() => _labeler.close();
}
Custom TFLite Model
Run your own trained TensorFlow Lite models for specialized tasks.
Project Setup
- Place your
.tflitemodel inassets/model/ - Add the model to
pubspec.yaml - Load and run the model
pubspec.yaml
flutter:
assets:
- assets/model/my_model.tflite
- assets/model/labels.txt
dependencies:
tflite_flutter: ^0.10.4
custom_classifier.dart
import 'dart:io';
import 'dart:typed_data';
import 'package:tflite_flutter/tflite_flutter.dart';
import 'package:image/image.dart' as img;
class CustomClassifier {
late Interpreter _interpreter;
List<String> _labels = [];
Future<void> loadModel() async {
_interpreter = await Interpreter.fromAsset(
'assets/model/my_model.tflite',
);
// Load labels
final labelData = await File('assets/model/labels.txt').readAsLines();
_labels = labelData;
}
Future<Map<String, double>> classifyImage(File imageFile) async {
// Resize image to model input size (e.g., 224x224)
final image = img.decodeImage(await imageFile.readAsBytes())!;
final resized = img.copyResize(image, width: 224, height: 224);
// Convert to input tensor
var input = List.generate(
224,
(y) => List.generate(
224,
(x) => [resized.getPixel(x, y).r / 255.0, resized.getPixel(x, y).g / 255.0, resized.getPixel(x, y).b / 255.0],
),
);
// Run inference
var output = List<List<double>>.filled(1, List.filled(_labels.length, 0.0));
_interpreter.run([input], output);
// Map results
final results = <String, double>{};
for (var i = 0; i < _labels.length; i++) {
results[_labels[i]] = output[0][i];
}
return results;
}
void dispose() => _interpreter.close();
}
Gemini API Integration
Google's Gemini API provides powerful generative AI capabilities for text, code, and multimodal content.
Setup
pubspec.yaml
dependencies:
google_generative_ai: ^0.4.3
gemini_service.dart
import 'package:google_generative_ai/google_generative_ai.dart';
class GeminiService {
late GenerativeModel _model;
GeminiService() {
_model = GenerativeModel(
model: 'gemini-1.5-flash',
apiKey: 'YOUR_API_KEY',
);
}
Future<String> generateText(String prompt) async {
final response = await _model.generateContent([Content.text(prompt)]);
return response.text ?? 'No response generated.';
}
Future<String> summarizeText(String text) async {
final response = await _model.generateContent([
Content.text('Summarize the following text concisely:\\n\\n$text'),
]);
return response.text ?? 'No summary generated.';
}
Future<String> chatWithContext(String message, List<Content> history) async {
final chat = _model.startChat(history: history);
final response = await chat.sendMessage(Content.text(message));
return response.text ?? 'No response.';
}
}
⚠️ API Key Security
Never hardcode API keys in production apps. Use environment variables,
--dart-define, or a backend proxy to keep keys secure.
Permissions Setup
ML features often require camera and storage permissions. Here's the setup for both platforms:
Android
AndroidManifest.xml
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera" android:required="false"/>
iOS
Info.plist
<key>NSCameraUsageDescription</key>
<string>This app needs camera access to scan text and barcodes.</string>
💡 Tip
Use the
permission_handler package to request runtime permissions in a cross-platform way.
Full Example: AI-Powered Scanner App
Here's a complete example combining OCR, barcode scanning, and image labeling into a single scanner screen:
ai_scanner_screen.dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'ocr_service.dart';
import 'barcode_service.dart';
import 'label_service.dart';
class AiScannerScreen extends StatefulWidget {
const AiScannerScreen({super.key});
@override
State<AiScannerScreen> createState() => _AiScannerScreenState();
}
class _AiScannerScreenState extends State<AiScannerScreen> {
final _ocr = OcrService();
final _barcode = BarcodeService();
final _labeling = ImageLabelService();
final _picker = ImagePicker();
String _result = '';
bool _loading = false;
Future<void> _analyze(ImageSource source) async {
final file = await _picker.pickImage(source: source);
if (file == null) return;
setState(() => _loading = true);
final imageFile = File(file.path);
// Run all three analyses in parallel
final results = await Future.wait([
_ocr.recognizeText(imageFile),
_barcode.scanBarcode(imageFile),
_labeling.labelImage(imageFile),
]);
final text = results[0] as String;
final barcode = results[1] as Barcode?;
final labels = results[2] as List;
setState(() {
_result = '📝 Text:\\n$text\\n\\n'
'📊 Barcode: ${barcode?.rawValue ?? "None"}\\n\\n'
'🏷️ Labels: ${labels.map((l) => l.label).join(", ")}';
_loading = false;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('AI Scanner')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton.icon(
onPressed: () => _analyze(ImageSource.camera),
icon: const Icon(Icons.camera_alt),
label: const Text('Camera'),
),
ElevatedButton.icon(
onPressed: () => _analyze(ImageSource.gallery),
icon: const Icon(Icons.photo_library),
label: const Text('Gallery'),
),
],
),
const SizedBox(height: 16),
if (_loading) const CircularProgressIndicator(),
if (!_loading && _result.isNotEmpty)
Expanded(child: SelectableText(_result)),
],
),
),
);
}
@override
void dispose() {
_ocr.dispose();
_barcode.dispose();
_labeling.dispose();
super.dispose();
}
}
Summary
- Text Recognition: Use
google_mlkit_text_recognitionfor OCR - Barcode Scanning: Use
google_mlkit_barcode_scanningfor QR/barcodes - Image Labeling: Use
google_mlkit_image_labelingfor object detection - Custom Models: Use
tflite_flutterfor specialized ML tasks - Generative AI: Use
google_generative_aifor Gemini API integration - Always dispose ML resources to prevent memory leaks
🚀 Next Step Explore Flutter Best Practices to learn about performance optimization and clean architecture patterns.