In Flutter, what does flutter create --template=package_ffi set up, and how do its build hook and ffigen config deliver a C library to the app?
answer
- hook/build.dart runs at build time
- native_toolchain_c CBuilder.library
- ffigen.yaml with ffi-native
- code asset named after the bindings file
- plugin_ffi is the deprecated path
basics
~20 spackage_ffi creates a package with C sources, a hook/build.dart that compiles them into a code asset with native_toolchain_c, and an ffigen.yaml that generates @Native bindings resolved against that asset, with no Gradle, CMake or podspec files.
solid answer
~40 sSince Flutter 3.38 the recommended starting point for C interop is `flutter create --template=package_ffi`. It generates `src/<name>.c` and `.h`, `lib/<name>_bindings_generated.dart`, `lib/<name>.dart` (the public API), `ffigen.yaml`, a test, and **`hook/build.dart`**. The build hook, stable since Dart 3.10, runs during `flutter build` and `flutter run` for each target; the template's hook uses `CBuilder.library` from `package:native_toolchain_c` with `assetName: '<name>_bindings_generated.dart'`, so the compiled library is registered as a code asset whose id matches the bindings library. The bindings, produced by `dart run ffigen --config ffigen.yaml` with the `ffi-native` option, are `@Native` external functions that resolve against that asset automatically: no per-OS build files, no `DynamicLibrary.open` paths. The older `plugin_ffi` template is deprecated in the tool; it remains for the Flutter plugin API or static linking on Apple platforms.
go deeper
Recall that package_ffi gives you C sources, a build hook that compiles them, and generated Dart bindings.
Explain how the hook's asset name and the @Native default assetId line up, and how ffigen's ffi-native option generates the bindings.
Handle system libraries and prebuilt binaries in hooks, keep names consistent across architectures and SDKs, and decide when plugin_ffi is still required.
Plan migration of existing plugin_ffi packages to hooks, weighing reduced build-file maintenance against features only the plugin template provides.
## The problem the template solves Shipping a C library in a Flutter app used to mean writing a build file per platform: Gradle and CMake for Android, a `.podspec` for iOS and macOS, `CMakeLists.txt` for Linux and Windows, plus Dart code that opened the right file on each OS. **Build hooks** (formerly *native assets*, stable in Dart 3.10) move that into one Dart script run by the SDK, and the Flutter documentation recommends the `package_ffi` template built on them since Flutter 3.38. ## What the template creates `flutter create --template=package_ffi imgz` produces: | File | Role | |---|---| | `src/imgz.c`, `src/imgz.h` | the C sources and header | | `hook/build.dart` | the **build hook**, run by the SDK to compile the C code | | `ffigen.yaml` | configuration for `package:ffigen` | | `lib/imgz_bindings_generated.dart` | generated `@Native` external functions | | `lib/imgz.dart` | the package's public Dart API wrapping the bindings | | `test/imgz_test.dart` | a unit test calling the native function | | `pubspec.yaml` | depends on `hooks`, `code_assets`, `native_toolchain_c`; `ffigen` and `ffi` as dev dependencies | ## The build hook ```dart import 'package:hooks/hooks.dart'; import 'package:logging/logging.dart'; import 'package:native_toolchain_c/native_toolchain_c.dart'; void main(List<String> args) async { await build(args, (input, output) async { final String packageName = input.packageName; final CBuilder cbuilder = CBuilder.library( name: packageName, assetName: '${packageName}_bindings_generated.dart', sources: ['src/$packageName.c'], ); await cbuilder.run(input: input, output: output, logger: Logger('')); }); } ``` - The SDK invokes the hook for each target OS and architecture it builds. - `CBuilder.library` compiles the sources with the platform's toolchain and adds a **code asset** to the output. - The **asset name** is the bindings library, so its id becomes `package:imgz/imgz_bindings_generated.dart`, exactly the default `assetId` of the `@Native` functions declared in that file. That is how the functions find their library without any loading code. - The same mechanism can **link a system library** (`LookupInProcess()`, or `DynamicLoadingSystem(...)` for a Windows DLL) or bundle a **prebuilt binary** downloaded and hash-verified in the hook (`DynamicLoadingBundled()`). ## The bindings ```bash dart run ffigen --config ffigen.yaml ``` `ffigen.yaml` names the header entry points, the output file, and sets `ffi-native:` so the generator emits `@Native` externals instead of a class wrapping a `DynamicLibrary`. Regenerate whenever the header changes, and review the diff: it is the contract between Dart and C. ## The public API layer The template's `lib/imgz.dart` shows the intended layering: - A short function is re-exported as a plain synchronous call. - A long-running function is wrapped in an asynchronous API that sends the work to a helper isolate, because a long native call on the main isolate blocks Dart execution and drops frames. ## When the legacy template still applies The Flutter tool prints that `plugin_ffi` is deprecated in favour of `package_ffi`. The documentation keeps it for three cases: code that needs the **Flutter plugin API**, **static linking** on iOS and macOS, and configuring a **Google Play services runtime** on Android. ## Pitfalls - Hook output names must be identical across architectures and across iOS device and simulator SDKs, or the tool cannot combine them into a universal binary or an XCFramework. - An app using `calloc` or `Arena` in its own code needs `package:ffi` as a regular dependency; the template only lists it for development.
- How does an @Native function in the generated bindings find the library the hook compiled?The hook registers its code asset with `assetName` set to the bindings file, giving the asset the id `package:<name>/<name>_bindings_generated.dart`. An `@Native` function without an explicit `assetId` defaults to its own library's URI, which is that same id, so the VM resolves the symbol in the bundled library.
- When would you still choose the plugin_ffi template?When the native code needs the Flutter plugin API (for example registering with the engine), must be statically linked on iOS or macOS, or needs a Google Play services runtime configured on Android. For a standalone C library such as an image compressor, `package_ffi` with a build hook is the recommended route.
saying these in an interview costs you the question
- Says package_ffi still needs a CMakeLists.txt and podspec per platform
- Believes the build hook runs on the user's device at runtime
- Hand-edits the generated bindings file instead of rerunning ffigen
- Adds architecture suffixes to the library name in the hook