OCR recognises text from an image. A recognised word is not a confirmed reading. Photos may contain serial numbers, dates and several numbers. Meter Reading Log therefore generates candidates and requires user confirmation. Vehicle File also uses a document scanner for PDF receipts without automatically analysing invoices. We model the shared workflow through a port and first test with controlled recognition.
01Define the recognition boundary
OcrPort returns recognised text lines. The learning adapter supplies fixed lines rather than a camera photo. This lets us run the domain workflow on any computer. It does not prove actual OCR quality.
abstract interface class OcrPort {
Future<List<String>> recognize(String image);
}
class LearningOcr implements OcrPort {
@override
Future<List<String>> recognize(String image) async => [
'Meter 12345',
'Reading 64820.5 kWh',
];
}
class CaptureService {
CaptureService(this.ocr);
final OcrPort ocr;
final saved = <String>[];
Future<List<String>> propose(String image) async {
final lines = await ocr.recognize(image);
final pattern = RegExp(r'\b\d{1,6}(?:[.,]\d{1,3})?\b');
return [
for (final line in lines)
for (final match in pattern.allMatches(line)) match.group(0)!,
];
}
void confirm(String candidate, {required bool accepted}) {
if (!accepted) return;
if (!RegExp(r'^\d{1,6}(?:[.,]\d{1,3})?$').hasMatch(candidate))
throw ArgumentError('Invalid value');
saved.add(candidate.replaceAll(',', '.'));
}
}
Future<void> main() async {
final service = CaptureService(LearningOcr());
final candidates = await service.propose('synthetic-photo');
if (service.saved.isNotEmpty) throw StateError('Saved before confirmation');
service.confirm(candidates.last, accepted: false);
if (service.saved.isNotEmpty) throw StateError('Saved after rejection');
service.confirm(candidates.last, accepted: true);
print(service.saved);
}
Run / check
dart run capture.dart02Generate candidates
The small regex finds numbers with an optional decimal part. It explicitly does not replace the real candidate extractor, which also considers lines, positions and separated digit groups. The learning example may return multiple suggestions.
03Keep confirmation as a separate action
propose writes no reading. confirm requires a candidate and an explicit decision. A real interface displays photo and value together, permits correction and only then validates the domain rule.
04Handle resources and cancellation
The real ML Kit adapter closes TextRecognizer in finally. Cancellation must not secretly confirm a draft. The service needs to distinguish camera failure, missing plugins and invalid results. The interface retains a useful retry path.
05Distinguish scanning from OCR
Vehicle File’s document scanner creates a PDF with editable scanned pages. The app deliberately copies the result into a record. Meter OCR instead generates a value suggestion. Both need device adapters but produce different domain results.
Before confirmation, saved is empty. Rejection leaves it empty. Confirmation adds exactly the selected value.
06Verify the real device workflow
On Android, test a clear photo, a blurred photo, missing permission, cancellation and multiple numbers. Check that no reading exists before confirmation. Browser tests do not replace this evidence. Google ML Kit scanner functions have the device and Play Services requirements documented for the product.
- Photo / scan
- Device port
- OCR candidates
- User review
- Domain rule / save
Your exercise
Add an OcrPort that throws an error. Verify that saved stays unchanged.
Show the solution and explanation
The exception must occur before confirm. Catch it at the application boundary and offer capture retry or manual input. Do not store an invented zero. An additional test checks a rejected candidate and an explicitly corrected value.
Project context and sources
The example is simplified for learning. The linked source version shows its use in the project and the checks applied there.