Table of Contents
Introduction to On-Device ML
In the era of mobile-first applications, on-device machine learning has become crucial for creating responsive, privacy-conscious, and intelligent apps. TensorFlow Lite brings the power of machine learning directly to mobile devices, enabling real-time inference without requiring an internet connection. This comprehensive guide will walk you through implementing TensorFlow Lite models in your Flutter applications.
On-device ML offers several advantages over cloud-based solutions: lower latency, offline functionality, enhanced privacy, and reduced server costs. With Flutter's cross-platform capabilities and TensorFlow Lite's optimized runtime, you can deploy sophisticated ML models across iOS and Android with a single codebase.
Why TensorFlow Lite for Flutter?
TensorFlow Lite is specifically designed for mobile and embedded devices, offering several key advantages for Flutter developers:
- Small Binary Size: Optimized for mobile with minimal footprint (< 1MB)
- Hardware Acceleration: Leverages GPU, DSP, and Neural Engine on supported devices
- Cross-Platform: Single model works on iOS, Android, and embedded systems
- Low Latency: Real-time inference with optimized operators
- Privacy-First: All processing happens on-device, no data leaves the phone
- Offline Capability: Works without internet connectivity
| Model Type | Size (MB) | Inference Time (ms) | Accuracy | Use Case |
|---|---|---|---|---|
| MobileNetV2 | 14 | 30-50 | 71.8% | Image Classification |
| SSD MobileNet | 27 | 50-80 | mAP: 22 | Object Detection |
| PoseNet | 13 | 40-60 | - | Pose Estimation |
| DeepLab v3 | 2.7 | 100-150 | mIOU: 77.21 | Segmentation |
Setting Up TensorFlow Lite
Let's start by setting up TensorFlow Lite in your Flutter project. We'll use the official TensorFlow Lite Flutter plugin and helper package.
1. Add Dependencies
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
tflite_flutter: ^0.10.0
tflite_flutter_helper: ^0.4.0
image_picker: ^1.0.0
camera: ^0.10.0
path_provider: ^2.0.0
image: ^4.0.0
2. Platform-Specific Setup
Android Configuration
// android/app/build.gradle
android {
...
aaptOptions {
noCompress 'tflite'
noCompress 'lite'
}
}
iOS Configuration
# ios/Podfile
platform :ios, '11.0'
# Add this at the end
pod 'TensorFlowLiteSwift'
3. Model Preparation
Place your .tflite model files in the assets folder:
# pubspec.yaml
flutter:
assets:
- assets/models/mobilenet_v2.tflite
- assets/models/labels.txt
Image Classification Implementation
Let's implement a complete image classification solution using MobileNet V2. This example demonstrates how to load a model, preprocess images, and interpret results.
Creating the Classifier Service
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:tflite_flutter/tflite_flutter.dart';
import 'package:tflite_flutter_helper/tflite_flutter_helper.dart';
import 'package:image/image.dart' as img;
class ImageClassifier {
late Interpreter _interpreter;
late List _labels;
late ImageProcessor _imageProcessor;
static const String modelFile = 'assets/models/mobilenet_v2.tflite';
static const String labelsFile = 'assets/models/labels.txt';
// Model input/output dimensions
static const int inputSize = 224;
static const int numResults = 5;
static const double threshold = 0.1;
ImageClassifier() {
_loadModel();
_loadLabels();
_setupImageProcessor();
}
Future _loadModel() async {
try {
// Load model from assets
_interpreter = await Interpreter.fromAsset(modelFile);
print('Model loaded successfully');
} catch (e) {
print('Failed to load model: $e');
throw Exception('Failed to load TensorFlow Lite model');
}
}
Future _loadLabels() async {
try {
final labelsData = await rootBundle.loadString(labelsFile);
_labels = labelsData.split('\n').where((label) => label.isNotEmpty).toList();
print('Loaded ${_labels.length} labels');
} catch (e) {
print('Failed to load labels: $e');
throw Exception('Failed to load labels');
}
}
void _setupImageProcessor() {
// Define preprocessing steps
_imageProcessor = ImageProcessorBuilder()
.add(ResizeOp(inputSize, inputSize, ResizeMethod.BILINEAR))
.add(NormalizeOp(0, 255)) // Normalize to [0, 1]
.add(QuantizeOp(0, 1)) // Quantize if using quantized model
.build();
}
Future> classifyImage(File imageFile) async {
try {
// Load and decode image
final imageBytes = await imageFile.readAsBytes();
img.Image? image = img.decodeImage(imageBytes);
if (image == null) {
throw Exception('Failed to decode image');
}
// Preprocess the image
final inputImage = TensorImage.fromImage(image);
final processedImage = _imageProcessor.process(inputImage);
// Prepare output tensor
final outputShape = _interpreter.getOutputTensor(0).shape;
final outputType = _interpreter.getOutputTensor(0).type;
final outputBuffer = TensorBuffer.createDynamic(outputType);
// Run inference
_interpreter.run(processedImage.buffer, outputBuffer.buffer);
// Process results
return _processResults(outputBuffer);
} catch (e) {
print('Classification failed: $e');
throw Exception('Failed to classify image');
}
}
List _processResults(TensorBuffer outputBuffer) {
// Get probabilities from output
final probabilities = outputBuffer.getDoubleList();
// Create list of recognitions
final recognitions = [];
for (int i = 0; i < probabilities.length; i++) {
if (probabilities[i] >= threshold) {
recognitions.add(Recognition(
id: i,
label: _labels[i],
confidence: probabilities[i],
));
}
}
// Sort by confidence
recognitions.sort((a, b) => b.confidence.compareTo(a.confidence));
// Return top results
return recognitions.take(numResults).toList();
}
void dispose() {
_interpreter.close();
}
}
class Recognition {
final int id;
final String label;
final double confidence;
Recognition({
required this.id,
required this.label,
required this.confidence,
});
String get displayText => '${(confidence * 100).toStringAsFixed(1)}% $label';
}
Building the UI
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
class ImageClassificationScreen extends StatefulWidget {
@override
_ImageClassificationScreenState createState() => _ImageClassificationScreenState();
}
class _ImageClassificationScreenState extends State {
final ImageClassifier _classifier = ImageClassifier();
final ImagePicker _picker = ImagePicker();
File? _image;
List? _results;
bool _isProcessing = false;
Future _pickImage(ImageSource source) async {
try {
final XFile? pickedFile = await _picker.pickImage(source: source);
if (pickedFile != null) {
setState(() {
_image = File(pickedFile.path);
_isProcessing = true;
});
// Classify the image
final results = await _classifier.classifyImage(_image!);
setState(() {
_results = results;
_isProcessing = false;
});
}
} catch (e) {
setState(() {
_isProcessing = false;
});
_showError('Failed to process image: $e');
}
}
void _showError(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message), backgroundColor: Colors.red),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Image Classification'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: Column(
children: [
// Image display area
Expanded(
child: Container(
margin: EdgeInsets.all(20),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(10),
),
child: _image == null
? Center(
child: Text(
'No image selected',
style: TextStyle(color: Colors.grey),
),
)
: Stack(
fit: StackFit.expand,
children: [
Image.file(_image!, fit: BoxFit.contain),
if (_isProcessing)
Container(
color: Colors.black54,
child: Center(
child: CircularProgressIndicator(),
),
),
],
),
),
),
// Results display
if (_results != null && _results!.isNotEmpty)
Container(
height: 200,
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Predictions:',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
SizedBox(height: 10),
Expanded(
child: ListView.builder(
itemCount: _results!.length,
itemBuilder: (context, index) {
final result = _results![index];
return ListTile(
title: Text(result.label),
trailing: Text(
'${(result.confidence * 100).toStringAsFixed(1)}%',
style: TextStyle(fontWeight: FontWeight.bold),
),
leading: Container(
width: 50,
child: LinearProgressIndicator(
value: result.confidence,
backgroundColor: Colors.grey[300],
valueColor: AlwaysStoppedAnimation(
result.confidence > 0.5 ? Colors.green : Colors.orange,
),
),
),
);
},
),
),
],
),
),
// Action buttons
Padding(
padding: EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton.icon(
onPressed: () => _pickImage(ImageSource.camera),
icon: Icon(Icons.camera_alt),
label: Text('Camera'),
),
ElevatedButton.icon(
onPressed: () => _pickImage(ImageSource.gallery),
icon: Icon(Icons.photo_library),
label: Text('Gallery'),
),
],
),
),
],
),
);
}
@override
void dispose() {
_classifier.dispose();
super.dispose();
}
}
Real-Time Object Detection
Now let's implement real-time object detection using the camera feed. We'll use SSD MobileNet for detecting multiple objects in real-time.
Object Detection Service
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:tflite_flutter/tflite_flutter.dart';
class ObjectDetector {
late Interpreter _interpreter;
late List _labels;
static const String modelFile = 'assets/models/ssd_mobilenet.tflite';
static const String labelsFile = 'assets/models/coco_labels.txt';
// Model parameters
static const int inputSize = 300;
static const int numDetections = 10;
static const double minConfidence = 0.5;
Future initialize() async {
// Load model
_interpreter = await Interpreter.fromAsset(modelFile);
// Load labels
final labelsData = await rootBundle.loadString(labelsFile);
_labels = labelsData.split('\n');
}
Future> detectObjects(CameraImage image) async {
// Convert CameraImage to input tensor
final input = _preprocessImage(image);
// Prepare output tensors
final outputLocations = List.filled(1 * numDetections * 4, 0.0).reshape([1, numDetections, 4]);
final outputClasses = List.filled(1 * numDetections, 0.0).reshape([1, numDetections]);
final outputScores = List.filled(1 * numDetections, 0.0).reshape([1, numDetections]);
final numDetections = List.filled(1, 0.0).reshape([1]);
// Create output map
final outputs = {
0: outputLocations,
1: outputClasses,
2: outputScores,
3: numDetections,
};
// Run inference
_interpreter.runForMultipleInputs([input], outputs);
// Process detections
return _processDetections(
outputLocations[0],
outputClasses[0],
outputScores[0],
);
}
List>> _preprocessImage(CameraImage image) {
// Convert YUV420 to RGB and resize
final convertedImage = _convertYUV420ToImage(image);
final resizedImage = _resizeImage(convertedImage, inputSize);
// Normalize pixel values
return _normalizeImage(resizedImage);
}
List _processDetections(
List> locations,
List classes,
List scores,
) {
final detections = [];
for (int i = 0; i < numDetections; i++) {
if (scores[i] >= minConfidence) {
final rect = Rect.fromLTRB(
locations[i][1],
locations[i][0],
locations[i][3],
locations[i][2],
);
detections.add(Detection(
label: _labels[classes[i].toInt()],
confidence: scores[i],
boundingBox: rect,
));
}
}
return detections;
}
void dispose() {
_interpreter.close();
}
}
class Detection {
final String label;
final double confidence;
final Rect boundingBox;
Detection({
required this.label,
required this.confidence,
required this.boundingBox,
});
}
Real-Time Camera View
class ObjectDetectionScreen extends StatefulWidget {
@override
_ObjectDetectionScreenState createState() => _ObjectDetectionScreenState();
}
class _ObjectDetectionScreenState extends State {
CameraController? _cameraController;
ObjectDetector? _detector;
List _detections = [];
bool _isDetecting = false;
@override
void initState() {
super.initState();
_initializeCamera();
_initializeDetector();
}
Future _initializeCamera() async {
final cameras = await availableCameras();
final camera = cameras.first;
_cameraController = CameraController(
camera,
ResolutionPreset.medium,
enableAudio: false,
);
await _cameraController!.initialize();
_cameraController!.startImageStream((CameraImage image) {
if (!_isDetecting) {
_isDetecting = true;
_detectObjects(image);
}
});
setState(() {});
}
Future _initializeDetector() async {
_detector = ObjectDetector();
await _detector!.initialize();
}
Future _detectObjects(CameraImage image) async {
try {
final detections = await _detector!.detectObjects(image);
setState(() {
_detections = detections;
});
} finally {
_isDetecting = false;
}
}
@override
Widget build(BuildContext context) {
if (_cameraController == null || !_cameraController!.value.isInitialized) {
return Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: [
CameraPreview(_cameraController!),
CustomPaint(
painter: DetectionPainter(_detections),
),
Positioned(
bottom: 20,
left: 20,
right: 20,
child: Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.circular(10),
),
child: Text(
'Detected: ${_detections.length} objects',
style: TextStyle(color: Colors.white, fontSize: 16),
textAlign: TextAlign.center,
),
),
),
],
),
);
}
@override
void dispose() {
_cameraController?.dispose();
_detector?.dispose();
super.dispose();
}
}
Training Custom Models
While pre-trained models are great for general use cases, you'll often need custom models for specific applications. Here's how to train and deploy custom TensorFlow Lite models.
Training with TensorFlow
# train_custom_model.py
import tensorflow as tf
from tensorflow import keras
import numpy as np
# Load and prepare dataset
(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()
x_train = x_train.astype('float32') / 255.0
x_test = x_test.astype('float32') / 255.0
# Define model architecture
model = keras.Sequential([
keras.layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)),
keras.layers.MaxPooling2D((2, 2)),
keras.layers.Conv2D(64, (3, 3), activation='relu'),
keras.layers.MaxPooling2D((2, 2)),
keras.layers.Conv2D(64, (3, 3), activation='relu'),
keras.layers.Flatten(),
keras.layers.Dense(64, activation='relu'),
keras.layers.Dense(10, activation='softmax')
])
# Compile and train
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
model.fit(
x_train, y_train,
epochs=10,
validation_data=(x_test, y_test)
)
# Convert to TensorFlow Lite
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()
# Save the model
with open('custom_model.tflite', 'wb') as f:
f.write(tflite_model)
print("Model converted and saved successfully!")
Model Quantization
# Quantization for smaller model size and faster inference
def representative_dataset():
for _ in range(100):
data = np.random.rand(1, 32, 32, 3).astype(np.float32)
yield [data]
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
converter.target_spec.supported_ops = [
tf.lite.OpsSet.TFLITE_BUILTINS_INT8
]
converter.inference_input_type = tf.uint8
converter.inference_output_type = tf.uint8
tflite_quant_model = converter.convert()
Model Optimization Techniques
Optimizing your models for mobile deployment is crucial for performance. Here are key techniques to reduce model size and improve inference speed.
1. Quantization
// Using quantized models in Flutter
class QuantizedModelHelper {
static Float32List quantizeInput(Float32List input, double scale, int zeroPoint) {
final quantized = Float32List(input.length);
for (int i = 0; i < input.length; i++) {
quantized[i] = (input[i] / scale + zeroPoint).clamp(0, 255);
}
return quantized;
}
static Float32List dequantizeOutput(Uint8List output, double scale, int zeroPoint) {
final dequantized = Float32List(output.length);
for (int i = 0; i < output.length; i++) {
dequantized[i] = (output[i] - zeroPoint) * scale;
}
return dequantized;
}
}
2. Model Pruning
# Prune model to remove unnecessary weights
import tensorflow_model_optimization as tfmot
pruning_params = {
'pruning_schedule': tfmot.sparsity.keras.PolynomialDecay(
initial_sparsity=0.0,
final_sparsity=0.5,
begin_step=0,
end_step=1000
)
}
model_for_pruning = tfmot.sparsity.keras.prune_low_magnitude(
model, **pruning_params
)
# Fine-tune the pruned model
model_for_pruning.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
model_for_pruning.fit(x_train, y_train, epochs=2)
3. Hardware Acceleration
// Enable GPU acceleration
class AcceleratedInterpreter {
static Future create(String modelPath) async {
final options = InterpreterOptions();
// Try to use GPU delegate
if (Platform.isAndroid) {
final gpuDelegate = GpuDelegateV2(
options: GpuDelegateOptionsV2(
inferencePreference: TfLiteGpuInferenceUsage.fastSingleAnswer,
inferencePriority1: TfLiteGpuInferencePriority.minLatency,
inferencePriority2: TfLiteGpuInferencePriority.minMemoryUsage,
inferencePriority3: TfLiteGpuInferencePriority.maxPrecision,
),
);
options.addDelegate(gpuDelegate);
} else if (Platform.isIOS) {
final metalDelegate = MetalDelegate(
options: MetalDelegateOptions(
allowPrecisionLoss: true,
waitType: TFLMetalDelegateWaitType.passive,
),
);
options.addDelegate(metalDelegate);
}
return await Interpreter.fromAsset(
modelPath,
options: options,
);
}
}
Performance Best Practices
1. Batch Processing
class BatchProcessor {
final int batchSize;
final Interpreter interpreter;
BatchProcessor({required this.batchSize, required this.interpreter});
Future>> processBatch(List images) async {
// Prepare batch input
final batchInput = List.generate(
batchSize,
(i) => i < images.length ? images[i] : Uint8List(0),
);
// Allocate output buffer
final output = List.generate(
batchSize,
(_) => List.filled(1000, 0.0), // Assuming 1000 classes
);
// Run batch inference
interpreter.run(batchInput, output);
// Return only valid results
return output.take(images.length).toList();
}
}
2. Memory Management
class ModelManager {
static final Map _interpreterCache = {};
static Future getInterpreter(String modelPath) async {
// Check cache first
if (_interpreterCache.containsKey(modelPath)) {
return _interpreterCache[modelPath]!;
}
// Load and cache new interpreter
final interpreter = await Interpreter.fromAsset(modelPath);
_interpreterCache[modelPath] = interpreter;
return interpreter;
}
static void releaseInterpreter(String modelPath) {
if (_interpreterCache.containsKey(modelPath)) {
_interpreterCache[modelPath]!.close();
_interpreterCache.remove(modelPath);
}
}
static void releaseAll() {
_interpreterCache.forEach((_, interpreter) {
interpreter.close();
});
_interpreterCache.clear();
}
}
3. Threading and Isolates
import 'dart:isolate';
class IsolateInference {
static Future> runInferenceInIsolate(
String modelPath,
Uint8List imageData,
) async {
final receivePort = ReceivePort();
await Isolate.spawn(
_inferenceIsolate,
IsolateData(
modelPath: modelPath,
imageData: imageData,
sendPort: receivePort.sendPort,
),
);
return await receivePort.first as List;
}
static void _inferenceIsolate(IsolateData data) async {
// Load model in isolate
final interpreter = await Interpreter.fromAsset(data.modelPath);
// Process image
final input = _preprocessImage(data.imageData);
final output = List.filled(1000, 0.0);
// Run inference
interpreter.run(input, output);
// Clean up
interpreter.close();
// Send results back
data.sendPort.send(output);
}
}
class IsolateData {
final String modelPath;
final Uint8List imageData;
final SendPort sendPort;
IsolateData({
required this.modelPath,
required this.imageData,
required this.sendPort,
});
}
Advanced Use Cases
1. Style Transfer
class StyleTransfer {
late Interpreter _interpreter;
Future applyStyle(
Uint8List contentImage,
Uint8List styleImage,
) async {
// Preprocess images
final content = _preprocessImage(contentImage, size: 384);
final style = _preprocessImage(styleImage, size: 256);
// Prepare output
final output = List.generate(
384,
(_) => List.generate(
384,
(_) => List.filled(3, 0.0),
),
);
// Run style transfer
_interpreter.runForMultipleInputs([content, style], [output]);
// Convert output to image
return _tensorToImage(output);
}
}
2. Text Recognition (OCR)
class TextRecognizer {
late Interpreter _detector;
late Interpreter _recognizer;
Future> recognizeText(Uint8List imageData) async {
// Step 1: Detect text regions
final regions = await _detectTextRegions(imageData);
// Step 2: Recognize text in each region
final textBlocks = [];
for (final region in regions) {
final croppedImage = _cropImage(imageData, region);
final text = await _recognizeTextInRegion(croppedImage);
textBlocks.add(TextBlock(
text: text,
boundingBox: region,
confidence: 0.95,
));
}
return textBlocks;
}
}
3. Pose Estimation
class PoseEstimator {
static const List bodyParts = [
'nose', 'leftEye', 'rightEye', 'leftEar', 'rightEar',
'leftShoulder', 'rightShoulder', 'leftElbow', 'rightElbow',
'leftWrist', 'rightWrist', 'leftHip', 'rightHip',
'leftKnee', 'rightKnee', 'leftAnkle', 'rightAnkle'
];
Future> estimatePose(Uint8List imageData) async {
// Preprocess image
final input = _preprocessImage(imageData);
// Output: 1 x 17 x 3 (17 keypoints, each with x, y, confidence)
final output = List.generate(
17,
(_) => List.filled(3, 0.0),
);
// Run inference
_interpreter.run(input, output);
// Convert to keypoints
final keypoints = [];
for (int i = 0; i < 17; i++) {
keypoints.add(Keypoint(
part: bodyParts[i],
x: output[i][0],
y: output[i][1],
confidence: output[i][2],
));
}
return keypoints;
}
}
Production Deployment
Model Versioning
class ModelVersionManager {
static const String prefsKey = 'model_versions';
static Future downloadAndUpdateModel(
String modelUrl,
String modelName,
String version,
) async {
try {
// Download model
final response = await http.get(Uri.parse(modelUrl));
if (response.statusCode == 200) {
// Save to app documents
final dir = await getApplicationDocumentsDirectory();
final file = File('${dir.path}/models/$modelName-$version.tflite');
await file.create(recursive: true);
await file.writeAsBytes(response.bodyBytes);
// Update version info
await _updateModelVersion(modelName, version, file.path);
}
} catch (e) {
print('Failed to download model: $e');
throw Exception('Model download failed');
}
}
static Future getModelPath(String modelName) async {
final prefs = await SharedPreferences.getInstance();
final versions = prefs.getString(prefsKey);
if (versions != null) {
final versionMap = json.decode(versions) as Map;
return versionMap[modelName]?['path'];
}
return null;
}
}
Performance Monitoring
class MLPerformanceMonitor {
static final Map> _inferenceTime = {};
static Future measureInference(
String modelName,
Future Function() inference,
) async {
final stopwatch = Stopwatch()..start();
try {
final result = await inference();
stopwatch.stop();
// Record inference time
_inferenceTime[modelName] ??= [];
_inferenceTime[modelName]!.add(stopwatch.elapsedMilliseconds.toDouble());
// Keep only last 100 measurements
if (_inferenceTime[modelName]!.length > 100) {
_inferenceTime[modelName]!.removeAt(0);
}
return result;
} catch (e) {
stopwatch.stop();
throw e;
}
}
static Map getStats() {
final stats = {};
_inferenceTime.forEach((model, times) {
if (times.isNotEmpty) {
final sorted = List.from(times)..sort();
stats[model] = PerformanceStats(
averageMs: times.reduce((a, b) => a + b) / times.length,
minMs: sorted.first,
maxMs: sorted.last,
p50Ms: sorted[sorted.length ~/ 2],
p95Ms: sorted[(sorted.length * 0.95).floor()],
sampleCount: times.length,
);
}
});
return stats;
}
}
Error Handling and Fallbacks
class RobustMLService {
Interpreter? _primaryInterpreter;
Interpreter? _fallbackInterpreter;
Future initialize() async {
try {
// Try to load optimized model
_primaryInterpreter = await Interpreter.fromAsset(
'models/optimized_model.tflite',
);
} catch (e) {
print('Failed to load optimized model: $e');
}
// Always load fallback model
_fallbackInterpreter = await Interpreter.fromAsset(
'models/fallback_model.tflite',
);
}
Future> runInference(Uint8List input) async {
// Try primary model first
if (_primaryInterpreter != null) {
try {
return await _runWithTimeout(
() => _executeInference(_primaryInterpreter!, input),
timeout: Duration(milliseconds: 100),
);
} catch (e) {
print('Primary model failed: $e');
}
}
// Fallback to secondary model
return await _executeInference(_fallbackInterpreter!, input);
}
Future _runWithTimeout(
Future Function() function, {
required Duration timeout,
}) async {
return await function().timeout(
timeout,
onTimeout: () => throw TimeoutException('Inference timeout'),
);
}
}
Conclusion
TensorFlow Lite empowers Flutter developers to build intelligent, responsive mobile applications with on-device machine learning. By following the techniques and best practices outlined in this guide, you can successfully implement everything from simple image classification to complex real-time object detection and custom model deployment.
Key Takeaways
- TensorFlow Lite provides efficient on-device ML with minimal overhead
- Proper preprocessing and postprocessing are crucial for model accuracy
- Model optimization techniques can significantly improve performance
- Hardware acceleration delivers up to 10x faster inference
- Isolates prevent UI blocking during inference
- Always implement proper error handling and fallback strategies
Next Steps
- Experiment with Models: Try different pre-trained models from TensorFlow Hub
- Train Custom Models: Build models specific to your use case
- Optimize Performance: Implement quantization and pruning
- Add Edge Features: Combine multiple models for complex scenarios
- Monitor Production: Track performance metrics and user experience