Skip to content

fix(iOS): build & test the example app via pure SwiftPM (d-date 9.0.2) + integration tests - #908

Merged
fbernaly merged 1 commit into
flutter-ml:feature/spmfrom
pumano:feature/spm-fixes
Nov 3, 2026
Merged

fbernaly merged 1 commit into
flutter-ml:feature/spmfrom
pumano:feature/spm-fixes

Conversation

@pumano

@pumano pumano commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Gets the example app building and its integration tests passing on the iOS
simulator using pure Swift Package Manager
(no CocoaPods), and adds an
integration test suite that exercises every ML Kit plugin without a physical
camera
.

On the iOS simulator the suite runs as +13 ~3 — 13 passed, 3 skipped,
0 failed.

The problem

feature/spm depended on mdata-group/google-mlkit-swiftpm at
9.0.0-simfix2. That package is unusable for a simulator build:

  • arm64-only simulator slices — the xcframeworks had no x86_64 simulator
    slice, so the universal build the Flutter tool forces
    (-destination "generic/platform=iOS Simulator") failed.
  • 33 KB stub binaries — the framework binaries were stubs, not the real ML
    Kit code.
  • No models — recognizers crashed at runtime with "Invalid model path".

mdata-group/google-mlkit-swiftpm is itself a fork of
d-date/google-mlkit-swiftpm. Switching to the upstream d-date package at a
proper release fixes all three at once.

The solution: d-date/google-mlkit-swiftpm 9.0.2

d-date repackages upstream ML Kit 9.0.0 as SPM binary-target
xcframeworks. Since 9.0.1 it:

  • ships proper universal slices — ios-arm64 (device) and
    ios-arm64_x86_64-simulator. The arm64 simulator slice is synthesised from the
    device slice by rewriting the Mach-O platform, so no EXCLUDED_ARCHS
    workaround
    is needed and the Flutter universal build works;
  • includes the models inside the xcframeworks for text recognition, pose
    detection and selfie segmentation;
  • is App Store Connect compatible.

Because SwiftPM cannot ship resource bundles inside a binary target, the model
resource bundles must be added to the app manually (see below).

What changed

Package manifests (11 Package.swift) — switched from
mdata-group … 9.0.0-simfix2 to d-date/google-mlkit-swiftpm exact: 9.0.2:
barcode_scanning, commons, digital_ink_recognition, entity_extraction,
face_detection, image_labeling, language_id, object_detection, pose_detection,
selfie_segmentation, smart_reply, text_recognition, translation.

Example app (packages/example)

  • ios/Runner.xcodeproj/project.pbxproj — adds the 19 ML Kit resource bundles
    (file refs + an "MLKit Bundles" group + Resources phase) and -all_load in
    the linker flags.
  • ios/mlkit_bundles/ — the 19 bundles, gitignored (≈50 MB of model files
    are not committed).
  • ios/download_mlkit_bundles.sh — new; fetches the bundles from the d-date
    release.
  • ios/.gitignore — ignores mlkit_bundles/.
  • ios/…/swiftpm/Package.resolved (×2) — pin d-date 9.0.2.
  • ios/Runner/AppDelegate.swift — registers a tiny example/simulator method
    channel so tests can detect the simulator.
  • pubspec.yaml / pubspec.lock — integration_test dev-dependency.
  • README.md — documents the one-time bundle download step.
  • integration_test/ml_kit_integration_test.dart — new; the test suite.

Plugin fix

  • google_mlkit_entity_extraction was a silent no-op: it only depended on
    commons, so #if canImport(MLKitEntityExtraction) was false and it returned
    UNIMPLEMENTED. Added the MLKitEntityExtraction product so it genuinely
    works.

How it works (technical notes)

  • The real ML Kit code lands in the app binary, not the frameworks. With
    -ObjC + -all_load, every object file in the ML Kit static archives is
    pulled into Runner.debug.dylib (≈530 MB in debug). The per-framework
    binaries copied into Runner.app/Frameworks/ are small stubs; the symbols
    (MLKTextRecognizer, MLKBarcodeScanner, MLKFaceDetector, …) live in the
    app dylib. This is expected for statically-linked ML Kit.
  • -all_load is required. Without it the ML Kit initializers (which
    register models/resources via +load) are dead-stripped and recognizers fail
    at runtime.
  • Plugin consistency. Every plugin that has an available d-date product is
    wired to it. The remaining stubs are correct because d-date 9.0.2 ships no
    such product: document_scanner, face_mesh_detection,
    subject_segmentation, and the six genai_* plugins.

Integration tests

integration_test/ml_kit_integration_test.dart covers every plugin. Two input
helpers avoid needing a real camera:

  • inputImageFromAsset(path) — copies a bundled asset to a temp file and builds
    InputImage.fromFilePath (the file-path native path).
  • bitmapFromAsset(path) — decodes an asset to raw RGBA and builds
    InputImage.fromBitmap (the same shape a camera frame produces).
Group Input Simulator behaviour
barcode scanning asset file runs
text recognition asset file runs
face detection asset file runs
face mesh detection asset file asserts MissingPluginException (no iOS API)
image labeling asset file runs
object detection asset file runs
pose detection asset file skips (MediaPipe GPU)
subject segmentation asset file asserts MissingPluginException (no iOS API)
selfie segmentation asset file skips (MediaPipe GPU)
digital ink recognition synthetic stroke runs
language identification string runs
entity extraction string runs
translation string (downloads model) runs
smart reply conversation runs
camera frame pipeline (emulated) asset → bitmap runs
camera (smoke) real camera skips (no camera / no permission)

All async ML Kit calls run inside tester.runAsync(...).

Why 3 tests are skipped

The 3 skips are all simulator limitations, not code problems:

Skipped test Why it skips on the simulator
pose detection ML Kit pose detection is MediaPipe-backed. The simulator has no real GPU, so MediaPipe's GL path hits LOG(FATAL) in mediapipe::GpuBufferStorageCvPixelBuffer::GetTexture → abort(). That is a hard crash of the whole app that cannot be caught from Dart, so the test detects the simulator (via the example/simulator channel) and skips. Runs on a real device.
selfie segmentation Same reason — also MediaPipe-backed (same GL path) → same crash → skipped. Runs on a real device.
camera (smoke) The simulator exposes no camera: availableCameras() is empty / initialize() throws (no permission granted). The test skips.

Note that face mesh and subject segmentation are not skipped: ML Kit has
no iOS API for them, so the plugins are graceful stubs returning
FlutterMethodNotImplemented. Those two tests pass on iOS by asserting the
MissingPluginException (and run the real detection on Android).

How to test

cd packages/example

# one-time: fetch the (gitignored) ML Kit model bundles
cd ios && ./download_mlkit_bundles.sh && cd ..

# ensure the Flutter SPM flag is on (one-time)
flutter config --enable-swift-package-manager

flutter build ios --simulator

flutter test integration_test/ml_kit_integration_test.dart -d <SIMULATOR_UDID>

flutter analyze on the example app: No issues found.

Caveats

  • Debug binary is large (≈530 MB Runner.debug.dylib) because -all_load
    pulls in all of ML Kit. Fine for tests; a release build is smaller.
  • Runtime model downloads. Digital-ink, entity-extraction and translation
    download their models on first use, so those tests need network access.
  • MediaPipe-backed modules (pose, selfie segmentation) are untestable on the
    simulator
    and must be verified on a real device.

The feature/spm branch depended on mdata-group/google-mlkit-swiftpm
9.0.0-simfix2, whose xcframeworks are arm64-only, are 33 KB stubs, and
ship no models — so the example app could not build for the simulator.

Switch to d-date/google-mlkit-swiftpm 9.0.2, which ships proper universal
slices (arm64 + x86_64 simulator), real binaries, and bundled models:

- Point all 11 plugin Package.swift manifests at d-date 9.0.2.
- Wire the 19 ML Kit resource bundles into the example Xcode project and
  add -all_load. Bundles are gitignored and fetched via
  ios/download_mlkit_bundles.sh (documented in the README).
- Fix entity_extraction, which was a silent no-op (it lacked the
  MLKitEntityExtraction product dependency, so canImport() was false).
- Add an integration test suite (integration_test/) that exercises every
  plugin from bundled assets with no physical camera. MediaPipe-backed
  tests (pose, selfie segmentation) self-skip on the simulator; the
  iOS-unavailable APIs (face mesh, subject segmentation) assert their
  graceful "not implemented" stub.

Result on the iOS simulator: 13 passed, 3 skipped, 0 failed.
@pumano
pumano force-pushed the feature/spm-fixes branch from 7d6aad4 to 3a09b7b Compare November 1, 2026 21:33
@pumano pumano changed the title iOS: build & test the example app via pure SwiftPM (d-date 9.0.2) + integration tests fix(iOS): build & test the example app via pure SwiftPM (d-date 9.0.2) + integration tests Nov 1, 2026
@pumano

pumano commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator Author

@fbernaly Could you check it via real iphone?

@fbernaly
fbernaly merged commit 4d3a219 into flutter-ml:feature/spm Nov 3, 2026
4 checks passed
@fbernaly

fbernaly commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator

@pumano : I tested on a physical device and everything looks good! I just pushed a few dependency version updates.

We're ready to merge this into develop and release a new plugin version. You've put a lot of work into this, so please branch off the latest code from feature/spm (including my latest commit) and open a PR against develop—I'd like you to get the credit for your contributions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants