Home
Dart
OOP
Flutter
Contact

Create AI-Powered Flutter Apps

A complete, step-by-step guide to building real AI applications from scratch using Flutter, Dart, and modern ML APIs.

📋 What You'll Build By the end of this guide you will have built five complete AI apps: a Gemini chat app, an image classifier, an OCR scanner, a voice assistant, and a barcode reader — all from the command line up.
← AI Integration Next: Best Practices →

1. Prerequisites & Environment Setup

Before building AI apps, you need a properly configured Flutter development environment. Follow every step below — skipping any command can cause hard-to-debug issues later.

Install Flutter SDK

On macOS (Apple Silicon), clone or download the Flutter SDK:

Terminal
# Clone the stable branch
git clone https://github.com/flutter/flutter.git -b stable ~/flutter

# Add Flutter to your PATH (zsh — macOS default)
echo 'export PATH="$HOME/flutter/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc

# Verify installation
flutter --version
# Expected output: Flutter 3.x.x • channel stable

Install Dart

Dart ships with Flutter, but you can also install it standalone for server-side work:

Terminal
brew install dart

# Verify
dart --version
# Dart SDK version: 3.x.x

Install Android Studio

Required for Android emulators and SDK tools:

Terminal
brew install --cask android-studio

# Launch Android Studio and follow the setup wizard
# Install: Android SDK, SDK Platform-Tools, Android Emulator

Install VS Code

Terminal
brew install --cask visual-studio-code

# Install Flutter and Dart extensions
code --install-extension Dart-Code.flutter
code --install-extension Dart-Code.dart-code

Run flutter doctor

This command checks your entire setup and tells you exactly what's missing:

Terminal
flutter doctor -v

Expected output will look something like this:

ComponentStatusAction Needed
Flutter SDKNone
Android ToolchainAccept licenses with flutter doctor --android-licenses
Xcode (iOS)Install Xcode from Mac App Store
Chrome (Web)Install Google Chrome
Android StudioInstall from brew or website
VS CodeInstall Flutter extension

Set Up Emulators / Simulators

Terminal
# List available Android emulators
flutter emulators

# Create a new Android emulator
flutter emulators --create --name pixel_7

# Launch the emulator
flutter emulators --launch pixel_7

# For iOS (macOS only)
open -a Simulator

# List connected devices
flutter devices
⚠️ Android Licenses Always run flutter doctor --android-licenses and accept every license. Missing licenses cause build failures with no clear error message.

2. Creating Your First AI App (Step by Step)

Let's walk through every single command from an empty directory to a running AI app on your device.

Step 1: Create the Project

Terminal
flutter create my_ai_app
cd my_ai_app

Step 2: Project Structure

After creation, your project looks like this:

PathPurpose
lib/main.dartYour app's entry point — all Dart code lives here
lib/Source code directory (add more .dart files here)
pubspec.yamlDependencies, assets, and project metadata
android/Android-specific native code and configs
ios/iOS-specific native code and configs
test/Unit and widget tests
build/Generated build output (never edit manually)
assets/Images, models, fonts (create this folder)

Step 3: Add Your First AI Package

Terminal
flutter pub add google_generative_ai

Step 4: Write the Code

lib/main.dart
import 'package:flutter/material.dart';
import 'package:google_generative_ai/google_generative_ai.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My AI App',
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});
  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  String _response = 'Tap the button to ask AI anything.';
  bool _loading = false;

  Future<void> _askAI() async {
    setState(() => _loading = true);
    final model = GenerativeModel(
      model: 'gemini-1.5-flash',
      apiKey: 'YOUR_API_KEY',
    );
    final response = await model.generateContent([
      Content.text('Say hello in exactly 5 words.'),
    ]);
    setState(() {
      _response = response.text ?? 'No response.';
      _loading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('My AI App')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(_response, textAlign: TextAlign.center),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: _loading ? null : _askAI,
              child: Text(_loading ? 'Thinking...' : 'Ask AI'),
            ),
          ],
        ),
      ),
    );
  }
}

Step 5: Run the App

Terminal
# List connected devices
flutter devices

# Run on a specific device
flutter run -d macOS
flutter run -d chrome
flutter run -d <device-id>

# Hot reload is automatic — edit lib/main.dart and save
💡 Hot Reload vs Hot Restart Hot Reload (press r) injects code changes without losing state. Hot Restart (press R) rebuilds the entire widget tree from scratch. Use reload for UI tweaks, restart for logic changes.

3. AI Chat App with Gemini API

Build a full conversational AI chat app with streaming responses using Google's Gemini API.

Create the Project

Terminal
flutter create ai_chat_app
cd ai_chat_app
flutter pub add google_generative_ai

Dependencies

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  google_generative_ai: ^0.4.3

Gemini Service Class

lib/services/gemini_service.dart
import 'dart:async';
import 'package:google_generative_ai/google_generative_ai.dart';

class GeminiChatService {
  late GenerativeModel _model;
  late ChatSession _chat;
  final List<Content> _history = [];

  GeminiChatService({required String apiKey}) {
    _model = GenerativeModel(
      model: 'gemini-1.5-flash',
      apiKey: apiKey,
      systemInstruction: Content.system(
        'You are a friendly AI assistant. Reply concisely.',
      ),
    );
    _chat = _model.startChat(history: _history);
  }

  Future<String> sendMessage(String message) async {
    final response = await _chat.sendMessage(Content.text(message));
    return response.text ?? 'No response.';
  }

  Stream<String> sendMessageStream(String message) async* {
    await for (final chunk in _chat.sendMessageStream(Content.text(message))) {
      if (chunk.text != null) {
        yield chunk.text!;
      }
    }
  }

  void resetChat() {
    _history.clear();
    _chat = _model.startChat(history: _history);
  }
}

Chat UI with Streaming

lib/screens/chat_screen.dart
import 'dart:async';
import 'package:flutter/material.dart';
import '../services/gemini_service.dart';

class ChatScreen extends StatefulWidget {
  const ChatScreen({super.key});
  @override
  State<ChatScreen> createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  late GeminiChatService _gemini;
  final List<Map<String, String>> _messages = [];
  final _controller = TextEditingController();
  final _scrollController = ScrollController();
  bool _isTyping = false;

  @override
  void initState() {
    super.initState();
    _gemini = GeminiChatService(apiKey: 'YOUR_API_KEY');
  }

  Future<void> _sendMessage() async {
    final text = _controller.text.trim();
    if (text.isEmpty) return;

    setState(() {
      _messages.add({'role': 'user', 'text': text});
      _controller.clear();
      _isTyping = true;
    });

    // Add empty assistant message for streaming
    setState(() => _messages.add({'role': 'assistant', 'text': ''}));

    await for (final chunk in _gemini.sendMessageStream(text)) {
      setState(() {
        _messages.last['text'] = (_messages.last['text'] ?? '') + chunk;
      });
      _scrollController.animateTo(
        _scrollController.position.maxScrollExtent,
        duration: const Duration(milliseconds: 100),
        curve: Curves.easeOut,
      );
    }

    setState(() => _isTyping = false);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('AI Chat')),
      body: Column(
        children: [
          Expanded(
            child: ListView.builder(
              controller: _scrollController,
              padding: const EdgeInsets.all(16),
              itemCount: _messages.length,
              itemBuilder: (context, index) {
                final msg = _messages[index];
                final isUser = msg['role'] == 'user';
                return Align(
                  alignment: isUser
                      ? Alignment.centerRight
                      : Alignment.centerLeft,
                  child: Container(
                    margin: const EdgeInsets.only(bottom: 8),
                    padding: const EdgeInsets.all(12),
                    decoration: BoxDecoration(
                      color: isUser
                          ? Theme.of(context).colorScheme.primary
                          : Theme.of(context).colorScheme.surfaceContainerHighest,
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: Text(msg['text'] ?? ''),
                  ),
                );
              },
            ),
          ),
          if (_isTyping)
            const Padding(
              padding: EdgeInsets.symmetric(horizontal: 16),
              child: LinearProgressIndicator(),
            ),
          SafeArea(
            child: Padding(
              padding: const EdgeInsets.all(8),
              child: Row(
                children: [
                  Expanded(
                    child: TextField(
                      controller: _controller,
                      onSubmitted: (_) => _sendMessage(),
                      decoration: const InputDecoration(
                        hintText: 'Type a message...',
                        border: OutlineInputBorder(),
                      ),
                    ),
                  ),
                  const SizedBox(width: 8),
                  IconButton(
                    onPressed: _isTyping ? null : _sendMessage,
                    icon: const Icon(Icons.send),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}
⚠️ API Key Security Never ship your API key in production. Use --dart-define=API_KEY=xxx at build time or route requests through a backend proxy.

4. Image Classification App with TFLite

Run TensorFlow Lite models on-device to classify images in real time — no internet required.

Create the Project

Terminal
flutter create image_classifier
cd image_classifier
flutter pub add tflite_flutter image_picker image

Add Model Assets

Download a MobileNet or EfficientNet .tflite model and its labels file, then:

Terminal
mkdir -p assets/model
cp ~/Downloads/mobilenet_v1.tflite assets/model/
cp ~/Downloads/labels.txt assets/model/
pubspec.yaml
flutter:
  assets:
    - assets/model/mobilenet_v1.tflite
    - assets/model/labels.txt

dependencies:
  tflite_flutter: ^0.10.4
  image_picker: ^1.0.7
  image: ^4.1.3

Classifier Service

lib/services/classifier_service.dart
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:tflite_flutter/tflite_flutter.dart';
import 'package:image/image.dart' as img;

class ClassifierService {
  late Interpreter _interpreter;
  List<String> _labels = [];
  static const int inputSize = 224;

  Future<void> loadModel() async {
    _interpreter = await Interpreter.fromAsset(
      'assets/model/mobilenet_v1.tflite',
    );
    final labelData = await rootBundle.loadString('assets/model/labels.txt');
    _labels = labelData.split('\n').where((l) => l.isNotEmpty).toList();
  }

  Future<Map<String, double>> classify(File imageFile) async {
    final bytes = await imageFile.readAsBytes();
    final image = img.decodeImage(bytes)!;
    final resized = img.copyResize(image, width: inputSize, height: inputSize);

    var input = List.generate(inputSize, (y) =>
      List.generate(inputSize, (x) => [
        resized.getPixel(x, y).r / 255.0,
        resized.getPixel(x, y).g / 255.0,
        resized.getPixel(x, y).b / 255.0,
      ]),
    );

    var output = List.filled(_labels.length, 0.0).reshape([1, _labels.length]);
    _interpreter.run([input], output);

    final results = <String, double>{};
    for (var i = 0; i < _labels.length; i++) {
      results[_labels[i]] = output[0][i];
    }

    return results;
  }

  List<MapEntry<String, double>> getTopResults(
    Map<String, double> results, {int top = 5}
  ) {
    final sorted = results.entries.toList()
      ..sort((a, b) => b.value.compareTo(a.value));
    return sorted.take(top).toList();
  }

  void dispose() => _interpreter.close();
}

Camera Integration UI

lib/screens/classifier_screen.dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import '../services/classifier_service.dart';

class ClassifierScreen extends StatefulWidget {
  const ClassifierScreen({super.key});
  @override
  State<ClassifierScreen> createState() => _ClassifierScreenState();
}

class _ClassifierScreenState extends State<ClassifierScreen> {
  final _classifier = ClassifierService();
  final _picker = ImagePicker();
  File? _image;
  List<MapEntry<String, double>> _results = [];
  bool _loading = false;

  @override
  void initState() {
    super.initState();
    _classifier.loadModel();
  }

  Future<void> _classify(ImageSource source) async {
    final picked = await _picker.pickImage(source: source);
    if (picked == null) return;

    setState(() {
      _image = File(picked.path);
      _loading = true;
    });

    final allResults = await _classifier.classify(_image!);
    setState(() {
      _results = _classifier.getTopResults(allResults);
      _loading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Image Classifier')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton.icon(
                  onPressed: () => _classify(ImageSource.camera),
                  icon: const Icon(Icons.camera_alt),
                  label: const Text('Camera'),
                ),
                ElevatedButton.icon(
                  onPressed: () => _classify(ImageSource.gallery),
                  icon: const Icon(Icons.photo_library),
                  label: const Text('Gallery'),
                ),
              ],
            ),
            const SizedBox(height: 16),
            if (_image != null)
              ClipRRect(
                borderRadius: BorderRadius.circular(12),
                child: Image.file(_image!, height: 200, fit: BoxFit.cover),
              ),
            const SizedBox(height: 16),
            if (_loading) const CircularProgressIndicator(),
            if (!_loading && _results.isNotEmpty)
              Expanded(
                child: ListView.builder(
                  itemCount: _results.length,
                  itemBuilder: (context, index) {
                    final entry = _results[index];
                    return ListTile(
                      title: Text(entry.key),
                      trailing: Text(
                        '${(entry.value * 100).toStringAsFixed(1)}%',
                      ),
                      leading: LinearProgressIndicator(
                        value: entry.value,
                        backgroundColor: Colors.grey[300],
                      ),
                    );
                  },
                ),
              ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _classifier.dispose();
    super.dispose();
  }
}
💡 Model Optimization For production, quantize your model to INT8 to reduce size by ~4x and improve inference speed on mobile devices. Use TensorFlow's post-training quantization tools.

5. Text Recognition (OCR) App

Extract text from photos, documents, and handwritten notes using Google ML Kit.

Create the Project

Terminal
flutter create ocr_app
cd ocr_app
flutter pub add google_mlkit_text_recognition image_picker

ML Kit Text Recognition Setup

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  google_mlkit_text_recognition: ^0.13.0
  image_picker: ^1.0.7

Android Permissions

android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-feature android:name="android.hardware.camera" android:required="false"/>
    <application ...>
    </application>
</manifest>

iOS Permissions

ios/Runner/Info.plist
<key>NSCameraUsageDescription</key>
<string>Camera access is needed to scan text from documents.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Photo library access is needed to pick images for text recognition.</string>

Camera/Gallery Image Picking & OCR

lib/screens/ocr_screen.dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';

class OcrScreen extends StatefulWidget {
  const OcrScreen({super.key});
  @override
  State<OcrScreen> createState() => _OcrScreenState();
}

class _OcrScreenState extends State<OcrScreen> {
  final _textRecognizer = TextRecognizer(script: TextRecognitionScript.latin);
  final _picker = ImagePicker();
  String _extractedText = 'No text extracted yet.';
  bool _isProcessing = false;

  Future<void> _pickAndRecognize(ImageSource source) async {
    final picked = await _picker.pickImage(source: source);
    if (picked == null) return;

    setState(() {
      _isProcessing = true;
      _extractedText = '';
    });

    final inputImage = InputImage.fromFilePath(picked.path);
    final recognized = await _textRecognizer.processImage(inputImage);

    setState(() {
      _extractedText = recognized.text.isEmpty
          ? 'No text found in image.'
          : recognized.text;
      _isProcessing = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('OCR Scanner')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton.icon(
                  onPressed: () => _pickAndRecognize(ImageSource.camera),
                  icon: const Icon(Icons.camera_alt),
                  label: const Text('Camera'),
                ),
                ElevatedButton.icon(
                  onPressed: () => _pickAndRecognize(ImageSource.gallery),
                  icon: const Icon(Icons.photo_library),
                  label: const Text('Gallery'),
                ),
              ],
            ),
            const SizedBox(height: 20),
            if (_isProcessing) const CircularProgressIndicator(),
            if (!_isProcessing)
              Expanded(
                child: SelectableText(
                  _extractedText,
                  style: const TextStyle(fontSize: 16),
                ),
              ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _textRecognizer.close();
    super.dispose();
  }
}
📋 Multiple Languages For non-Latin scripts, create separate TextRecognizer instances with TextRecognitionScript.chinese, TextRecognitionScript.japanese, or TextRecognitionScript.korean.

6. Voice Assistant App with Speech-to-Text

Build an app that listens to voice input, converts it to text, and sends it to an AI for a response.

Create the Project

Terminal
flutter create voice_assistant
cd voice_assistant
flutter pub add speech_to_text google_generative_ai

Dependencies

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  speech_to_text: ^6.6.0
  google_generative_ai: ^0.4.3

Microphone Permission Setup

Add microphone permission to both platforms:

android/app/src/main/AndroidManifest.xml
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
ios/Runner/Info.plist
<key>NSMicrophoneUsageDescription</key>
<string>Microphone access is needed for voice input.</string>

Speech-to-Text + AI Service

lib/services/speech_ai_service.dart
import 'dart:async';
import 'package:speech_to_text/speech_to_text.dart';
import 'package:google_generative_ai/google_generative_ai.dart';

class SpeechAiService {
  final _speech = SpeechToText();
  late GenerativeModel _model;
  bool _isListening = false;

  SpeechAiService({required String apiKey}) {
    _model = GenerativeModel(model: 'gemini-1.5-flash', apiKey: apiKey);
  }

  Future<bool> initialize() async {
    return await _speech.initialize(
      onError: (error) => print('Speech error: $error'),
      onStatus: (status) => print('Speech status: $status'),
    );
  }

  void startListening({required Function(String) onResult}) {
    if (_isListening) return;
    _isListening = true;
    _speech.listen(
      onResult: (result) {
        if (result.recognizedWords.isNotEmpty) {
          onResult(result.recognizedWords);
        }
      },
      listenFor: const Duration(seconds: 30),
      pauseFor: const Duration(seconds: 3),
    );
  }

  void stopListening() {
    _speech.stop();
    _isListening = false;
  }

  Future<String> getAiResponse(String userInput) async {
    final response = await _model.generateContent([
      Content.text('Answer concisely: $userInput'),
    ]);
    return response.text ?? 'Sorry, no response.';
  }

  bool get isListening => _isListening;
}

Voice Assistant UI

lib/screens/voice_screen.dart
import 'package:flutter/material.dart';
import '../services/speech_ai_service.dart';

class VoiceScreen extends StatefulWidget {
  const VoiceScreen({super.key});
  @override
  State<VoiceScreen> createState() => _VoiceScreenState();
}

class _VoiceScreenState extends State<VoiceScreen> {
  late SpeechAiService _service;
  String _transcript = '';
  String _aiResponse = '';
  bool _isProcessing = false;

  @override
  void initState() {
    super.initState();
    _service = SpeechAiService(apiKey: 'YOUR_API_KEY');
    _service.initialize();
  }

  Future<void> _toggleListening() async {
    if (_service.isListening) {
      _service.stopListening();
      return;
    }

    _service.startListening(onResult: (text) async {
      setState(() => _transcript = text);

      if (!_service.isListening && text.isNotEmpty) {
        setState(() => _isProcessing = true);
        final response = await _service.getAiResponse(text);
        setState(() {
          _aiResponse = response;
          _isProcessing = false;
        });
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Voice Assistant')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            GestureDetector(
              onTap: _toggleListening,
              child: CircleAvatar(
                radius: 50,
                backgroundColor: _service.isListening
                    ? Colors.red
                    : Theme.of(context).colorScheme.primary,
                child: Icon(
                  _service.isListening ? Icons.mic_off : Icons.mic,
                  size: 40,
                  color: Colors.white,
                ),
              ),
            ),
            const SizedBox(height: 24),
            Text(_transcript, style: const TextStyle(fontSize: 18)),
            const SizedBox(height: 16),
            if (_isProcessing) const CircularProgressIndicator(),
            if (!_isProcessing && _aiResponse.isNotEmpty)
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 32),
                child: Text(_aiResponse, style: const TextStyle(fontSize: 16)),
              ),
          ],
        ),
      ),
    );
  }
}
⚠️ iOS Background Audio If you need continuous listening, add the UIBackgroundModes key with audio value to your Info.plist. Without it, iOS will stop recording when the app goes to background.

7. Barcode & QR Scanner App

Scan QR codes, EAN-13, Code 128, and 20+ barcode formats with real-time camera preview.

Create the Project

Terminal
flutter create barcode_scanner
cd barcode_scanner
flutter pub add google_mlkit_barcode_scanning camera

Dependencies

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  google_mlkit_barcode_scanning: ^0.13.0
  camera: ^0.10.5+9

Permissions

android/app/src/main/AndroidManifest.xml
<uses-permission android:name="android.permission.CAMERA"/>
ios/Runner/Info.plist
<key>NSCameraUsageDescription</key>
<string>Camera access is needed to scan barcodes and QR codes.</string>

Barcode Scanner Service

lib/services/barcode_service.dart
import 'dart:io';
import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart';

class BarcodeService {
  final _scanner = BarcodeScanner(
    formats: [
      BarcodeFormat.qrCode,
      BarcodeFormat.ean13,
      BarcodeFormat.ean8,
      BarcodeFormat.code128,
    ],
  );

  Future<List<Barcode>> scanFromImage(File imageFile) async {
    final inputImage = InputImage.fromFile(imageFile);
    return await _scanner.processImage(inputImage);
  }

  void dispose() => _scanner.close();
}

Real-Time Camera Scanning

lib/screens/scanner_screen.dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart';

class ScannerScreen extends StatefulWidget {
  const ScannerScreen({super.key});
  @override
  State<ScannerScreen> createState() => _ScannerScreenState();
}

class _ScannerScreenState extends State<ScannerScreen> {
  late CameraController _cameraController;
  final _barcodeScanner = BarcodeScanner();
  bool _isScanning = false;
  String _result = 'Point camera at a barcode';

  @override
  void initState() {
    super.initState();
    _initCamera();
  }

  Future<void> _initCamera() async {
    final cameras = await availableCameras();
    _cameraController = CameraController(
      cameras.first,
      ResolutionPreset.medium,
      enableAudio: false,
    );
    await _cameraController.initialize();
    _cameraController.startImageStream(_processImage);
    setState(() {});
  }

  void _processImage(CameraImage image) async {
    if (_isScanning) return;
    _isScanning = true;

    // Convert camera image to InputImage and scan
    final inputImage = InputImage.fromCameraImage(image);
    if (inputImage == null) {
      _isScanning = false;
      return;
    }

    final barcodes = await _barcodeScanner.processImage(inputImage);
    if (barcodes.isNotEmpty) {
      final barcode = barcodes.first;
      setState(() {
        _result = '${barcode.type.name}: ${barcode.rawValue}';
      });
      _cameraController.stopImageStream();
    }

    _isScanning = false;
  }

  @override
  Widget build(BuildContext context) {
    if (!_cameraController.value.isInitialized) {
      return const Center(child: CircularProgressIndicator());
    }

    return Scaffold(
      appBar: AppBar(title: const Text('Barcode Scanner')),
      body: Column(
        children: [
          Expanded(
            child: CameraPreview(_cameraController),
          ),
          Container(
            padding: const EdgeInsets.all(16),
            color: Colors.black87,
            width: double.infinity,
            child: Text(
              _result,
              style: const TextStyle(color: Colors.white, fontSize: 18),
              textAlign: TextAlign.center,
            ),
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _cameraController.dispose();
    _barcodeScanner.close();
    super.dispose();
  }
}
💡 Supported Barcode Formats ML Kit supports 20+ formats: QR Code, EAN-13, EAN-8, UPC-A, UPC-E, Code 128, Code 39, Code 93, ITF, Codabar, Data Matrix, PDF417, Aztec, and more. Filter formats in the BarcodeScanner constructor for better performance.

8. Deploying Your AI App

Once your app is ready, you need to build release binaries and submit them to app stores.

Build Android APK

Terminal
# Build a release APK (arm64-v8a)
flutter build apk --release

# Build a split APK (smaller per-device downloads)
flutter build apk --split-per-abi

# Build an app bundle (recommended for Play Store)
flutter build appbundle --release

The APK output is at build/app/outputs/flutter-apk/app-release.apk.

Build iOS IPA

Terminal
# Build for iOS release
flutter build ios --release

# Then archive in Xcode for distribution
open ios/Runner.xcworkspace

Play Store Submission

  1. Create a developer account at play.google.com/console
  2. Create a new app and fill in store listing details
  3. Upload the .aab file from build/app/outputs/bundle/release/
  4. Set pricing, content rating, and target audience
  5. Add screenshots for phone and tablet
  6. Submit for review (typically 1-3 days)

App Store Submission

  1. Enroll in the Apple Developer Program ($99/year)
  2. Archive your app in Xcode (Product → Archive)
  3. Distribute to App Store Connect
  4. Fill in metadata, screenshots, and privacy policy URL
  5. Submit for App Review (typically 1-7 days)

Performance Optimization

TechniqueImpactCommand / Tool
Tree shakingReduces APK size 30-50%flutter build apk --tree-shake-icons
Split APKsSmaller per-device downloadsflutter build apk --split-per-abi
Profile modeIdentify slow framesflutter run --profile
DevToolsMemory leaks, widget rebuildsflutter pub global activate devtools
Deferred loadingFaster initial startupUse deferred imports in Dart
Model quantizationSmaller ML models, faster inferenceTensorFlow Lite converter
⚠️ API Key Rotation Before publishing, rotate all API keys and ensure they're stored securely. Use --dart-define-from-file=secrets.json to inject keys at build time without committing them to source control.

9. Complete Command Reference

Every Flutter command used in this guide, organized for quick reference.

Project Commands

CommandDescription
flutter create my_appCreate a new Flutter project
flutter pub add package_nameAdd a dependency
flutter pub getFetch all dependencies
flutter pub outdatedShow packages with newer versions
flutter pub upgradeUpgrade dependencies to latest compatible
flutter cleanRemove build artifacts
flutter pub cache cleanClear the pub cache

Build & Run Commands

CommandDescription
flutter runRun the app on connected device
flutter run -d chromeRun on Chrome (web)
flutter run --releaseRun in release mode
flutter run --profileRun in profile mode (performance testing)
flutter build apk --releaseBuild release APK
flutter build appbundle --releaseBuild release AAB for Play Store
flutter build ios --releaseBuild release iOS
flutter build webBuild for web deployment

Debugging Commands

CommandDescription
flutter doctorCheck development environment
flutter doctor -vDetailed environment check
flutter devicesList connected devices
flutter emulatorsList available emulators
flutter emulators --launch nameLaunch an emulator
flutter analyzeRun static analysis for errors
flutter testRun unit and widget tests
flutter logsView device logs in real time
flutter inspectOpen DevTools inspector

Common Troubleshooting

ProblemSolution
Flutter SDK not foundAdd flutter/bin to your PATH and restart terminal
No connected devicesRun flutter devices and check USB debugging / simulator
Android license not acceptedRun flutter doctor --android-licenses and accept all
Pod install failed (iOS)Run cd ios && pod install --repo-update
Gradle build failedRun flutter clean && flutter pub get && flutter run
Package version conflictCheck flutter pub outdated and update pubspec.yaml
Hot reload not workingEnsure lib/main.dart has no compile errors; try hot restart (R)
ML Kit build errorCheck minSdkVersion is at least 21 in android/app/build.gradle

Package Version Compatibility

Use these tested package versions together for maximum compatibility:

PackageMin FlutterTested Version
google_generative_ai3.16^0.4.3
google_mlkit_text_recognition3.0^0.13.0
google_mlkit_barcode_scanning3.0^0.13.0
tflite_flutter3.0^0.10.4
speech_to_text3.0^6.6.0
image_picker3.0^1.0.7
camera3.0^0.10.5+9
🚀 What's Next? You now have five complete AI app templates. Combine them — add OCR to your chat app, or run TFLite models on camera frames in your barcode scanner. Read Flutter Best Practices to learn clean architecture and performance patterns.
K
Kesavaraja Murugesan
Flutter Developer & Educator

Flutter developer with 3 years of experience building production-grade mobile applications. Passionate about teaching clean architecture patterns and helping developers write maintainable, scalable code.

← AI Integration Next: Best Practices →