Compatibility
Plugin discovery, the host envelope, FFI validation, and how SDK and plugin versions relate.
A bundle runs only on a host that can provide everything it needs. The loader checks that before any bytecode runs, and every check fails with a distinct, attributable error: a bundle-side fault ("Bundle invalid" — rebuild the bundle or add something to the host) or a loader-side fault ("Loader build is broken" — the host build itself is wrong). See How it works for the concept; this page covers what the plugins actually compare.
The host defines the envelope
Everything native lives in the host: the Flutter engine, the compiled Dart runtime, every plugin's platform code, and every native library. A bundle only references them. The rule that follows is the one requirement you maintain by hand:
Your host's
pubspec.yamlmust declare every native plugin your bundles intend to call.
A bundle that needs a plugin the host does not ship is rejected up front. Adding
a plugin to a bundle therefore means adding it to the host's pubspec.yaml,
running flutter pub get, and rebuilding the host — after that, the new
available-plugins asset is derived on the next build automatically.
At load time the loader verifies, in order:
| Manifest field | Compared against | On failure |
|---|---|---|
flitz_version | Must equal 2 | Bundle invalid: Unsupported bundle version |
platform_dill_hash | The host's expected hash from flitz_loader_host_config.json | Bundle invalid: Platform dill hash mismatch |
required_plugins | The names in flitz_loader_available_plugins.json | Bundle invalid: Bundle requires plugins not available in this host |
required_native_assets | The asset ids in the host's flutter_assets/NativeAssetsManifest.json for the current target | Bundle invalid: Bundle requires native asset ids not available in this host |
Plugin discovery
The loader cannot know which plugins your app ships when the AAR or xcframework
is built — that lives in your pubspec.yaml. Both plugins therefore derive the
list at your build time and place it in the app as
flitz_loader_available_plugins.json: a JSON array of pub package names, the
union of plugins.{android,ios,linux,macos,windows,web}[].name and
dependencyGraph[].name from .flutter-plugins-dependencies (the file
flutter pub get writes at the project root).
- Android: the Gradle plugin's
flitzGenerateAvailablePlugins<Variant>task writes it into the variant's generated assets; it lands atassets/flitz_loader_available_plugins.json. Inspect a build withunzip -p app-release.apk assets/flitz_loader_available_plugins.json. - iOS: the CocoaPods script phase or the SwiftPM
FlitzAvailablePluginsbuild-tool plugin writes it into theFlitzLoaderAvailablePlugins.bundleresource insideRunner.app. With SwiftPM you must enable the build-tool plugin on theRunnertarget; see the iOS quickstart.
The runtime check has three states:
| Asset state | Behavior |
|---|---|
| Present | Bundles whose required_plugins is not a subset are rejected. The error lists what is missing and what is available. |
| Missing | The check is skipped with a logged warning. A bundle that calls an undeclared plugin fails later with MissingPluginException at method-channel time. This happens only when the build-time hook is not in place (the SwiftPM plugin not enabled, or a non-standard integration). |
| Malformed | "Loader build is broken" — the host's build pipeline produced something other than a JSON array of strings. |
Registering the plugins on the bundle's engine is a separate step from validating them:
- Android:
FlutterEngine(context)registers your plugins reflectively throughio.flutter.plugins.GeneratedPluginRegistrant, generated in your package. Nothing to write. - iOS: the loader calls your
registerPlugins(with:)override once per bundle engine, before it runs. The override is required; without it the bundle has no plugins at all.
Plugins that rely on FlutterPluginRegistrar.addApplicationDelegate(_:) may not
work inside bundles, because the bundle engine's registrar is invoked outside
the standard app-delegate forwarding chain.
FFI
Bundles may call native code through dart:ffi, but only native code the host
already ships. A hook-based plugin (hook/build.dart) emits per-target code
assets into the host's compiled flutter_assets/NativeAssetsManifest.json
during your flutter build; a bundle's manifest carries only
required_native_assets, a target-agnostic list of asset ids.
Before running a bundle the loader:
- Reads the host's
NativeAssetsManifest.json— on Android from the APK'sflutter_assets/, on iOS fromApp.framework/flutter_assets/— and selects the entries for the current target (android_arm64orios_arm64). - Accepts only the
absoluteandprocessasset kinds.relative,system, andexecutablemark a broken host build and fail as "Loader build is broken" — as does a manifest that fails to parse. - Rejects the bundle if any required asset id is not provided by the host.
- Copies the host's manifest into the extracted bundle's
flutter_assets/, so the engine resolves asset ids against the single runtime authority.
In practice: if a bundle uses hello_ffi_plugin, the host's pubspec.yaml must
list hello_ffi_plugin (directly or transitively) so its native library and
manifest entry are compiled into the host. A bundle cannot bring its own native
code — an unknown asset id fails the subset check, and there is no runtime
download or dlopen of a path the bundle controls.
SDK and plugin versions
Two version surfaces meet in the loader, and they are deliberately decoupled.
The Flitz SDK tag is what your host and your bundles are built with. The
loader plugins carry no baked SDK identity: at your build time, the Gradle
task flitzGenerateHostConfig<Variant> (Android) or the CocoaPods / SwiftPM
hook (iOS) hashes the platform_strong.dill files in your FLUTTER_ROOT and
writes flitz_loader_host_config.json into the app, alongside the SDK tag for
diagnostics. If that path is not a Flitz SDK, the build fails naming the
missing file — a host that cannot verify bundles is never produced.
At load time the loader picks the release hash for a release host and the
profile hash for a profile (or debug) host and compares it with the bundle's
platform_dill_hash. Because the hash is derived from the same SDK that
supplied the host's engine, the check compares the bundle against the engine
that will actually run it. A mismatch means the bundle was built against a
different SDK release than the host; the error names both hashes and the
host's SDK tag. Rebuild the bundle with flitz publish against the host's pin,
or rebuild the host.
The loader-plugin version follows its own semver, independent of the SDK.
Each published plugin version declares a compatibility floor (the lowest SDK tag
whose embedding API it supports); flitz plugins configure picks the newest
plugin version whose floor is at or below your pinned SDK tag and prints it.
The recommendation is exactly that — the CLI never reads your Gradle, Podfile,
or SwiftPM files, so it reports what your project should use, not what it
does use. Run it again after changing your SDK pin, and use flitz status to
check for drift between your pin and your toolchain.
Runtime compatibility is enforced by the hash, never by a version string. A plugin version that is too old for your SDK's embedding API surfaces at build or link time, not as a bundle rejection.