Flitzdocs
Loader plugins

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.yaml must 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 fieldCompared againstOn failure
flitz_versionMust equal 2Bundle invalid: Unsupported bundle version
platform_dill_hashThe host's expected hash from flitz_loader_host_config.jsonBundle invalid: Platform dill hash mismatch
required_pluginsThe names in flitz_loader_available_plugins.jsonBundle invalid: Bundle requires plugins not available in this host
required_native_assetsThe asset ids in the host's flutter_assets/NativeAssetsManifest.json for the current targetBundle 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 at assets/flitz_loader_available_plugins.json. Inspect a build with unzip -p app-release.apk assets/flitz_loader_available_plugins.json.
  • iOS: the CocoaPods script phase or the SwiftPM FlitzAvailablePlugins build-tool plugin writes it into the FlitzLoaderAvailablePlugins.bundle resource inside Runner.app. With SwiftPM you must enable the build-tool plugin on the Runner target; see the iOS quickstart.

The runtime check has three states:

Asset stateBehavior
PresentBundles whose required_plugins is not a subset are rejected. The error lists what is missing and what is available.
MissingThe 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 through io.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:

  1. Reads the host's NativeAssetsManifest.json — on Android from the APK's flutter_assets/, on iOS from App.framework/flutter_assets/ — and selects the entries for the current target (android_arm64 or ios_arm64).
  2. Accepts only the absolute and process asset kinds. relative, system, and executable mark a broken host build and fail as "Loader build is broken" — as does a manifest that fails to parse.
  3. Rejects the bundle if any required asset id is not provided by the host.
  4. 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.

On this page