In Flutter, what does dart:ffi let you do, and when would you call a C library through it instead of a MethodChannel?
answer
- C ABI, not Kotlin or Swift
- synchronous call on the calling thread
- no codec, no message hop
- not available on the web
- native memory is yours to manage
basics
~20 sdart:ffi calls C-ABI functions and reads or writes native memory directly from Dart, synchronously and without a codec. Use it for a C library such as an image compressor; use a MethodChannel for Kotlin or Swift platform APIs.
solid answer
~50 s`dart:ffi` is Dart's **foreign function interface**: Dart code declares the C signature of a function, binds it to a symbol in a native library, and calls it like a Dart function. The call is **synchronous** on the calling isolate's thread, arguments are passed as C values or pointers, and nothing is serialised. That makes it the right tool for a portable C (or C-compatible) library, such as an image-compression library in a photo editor, or for tight loops over pixel buffers. A `MethodChannel` is the right tool for Kotlin or Swift platform APIs: it is asynchronous, encodes values with a codec, and runs the handler on the platform thread. FFI trade-offs: you manage native memory yourself, a long call blocks the isolate that made it, a wrong signature crashes the process rather than throwing, and `dart:ffi` does not exist on the web.
go deeper
Recall that dart:ffi calls C functions directly and synchronously, while channels send asynchronous messages to Kotlin or Swift code.
Explain the trade-offs: no codec and synchronous results versus manual memory, blocking calls and crash-prone signatures, and the lack of web support.
Choose between FFI, channels and generated bindings for a real library, and plan how blocking calls and native memory are contained.
Decide whether a team should own C code at all, weighing performance against build complexity, crash triage and platform coverage.
## Two ways across the boundary Flutter code runs in Dart. Native code runs outside the Dart VM. There are two mainstream ways across: - **Platform channels** (`MethodChannel` and friends): asynchronous messages, encoded by a codec, handled by Kotlin, Swift or C++ code registered with the Flutter engine. - **`dart:ffi`**: a **foreign function interface** that calls functions following the **C calling convention (ABI)** directly, and reads and writes native memory through typed `Pointer`s. ## What an FFI call looks like For a C function `int32_t imgz_compress_jpeg(...)`, Dart declares both the native signature and the Dart signature and binds them to the symbol, either with an `@Native` external function or with `DynamicLibrary.open(...).lookupFunction<...>()`. From then on, calling it is an ordinary Dart function call: ```dart @Native<Int32 Function(Pointer<Uint8>, Int32, Int32, Int32)>() external int imgz_quick_check(Pointer<Uint8> rgba, int width, int height, int quality); ``` ## How the two compare | | `dart:ffi` | `MethodChannel` | |---|---|---| | Target | C-ABI functions (C, or C++ / Rust / other code exported with C linkage) | Kotlin/Java, Swift/Objective-C, C++ plugin handlers | | Call style | synchronous, returns a value | asynchronous, returns a `Future` | | Data | C numbers, pointers, structs, shared buffers | codec-supported values, copied | | Thread | the calling isolate's thread | host handler on the platform main thread by default | | Failure mode | wrong signature or bad pointer can crash the process | `PlatformException` / `MissingPluginException` | | Platforms | Android, iOS, desktop | every platform, including web through web plugins | ## When FFI is the better choice 1. **A portable C library** you want on every native platform with one Dart binding: codecs, compression, cryptography, databases, a physics or image-processing engine. 2. **High call frequency or large buffers**: FFI has no encoding step and can hand native code a pointer to pixel data instead of copying it through a codec. 3. **Synchronous answers**: FFI returns a value immediately, which a channel cannot. ## When a channel (or a plugin) is better - The API is a platform SDK exposed in Kotlin or Swift (camera, sensors, payments). Channels, or Pigeon-generated channels, are the usual route. - The app targets the web: `dart:ffi` is only available on the Dart native platforms, not in browser builds. - The team has no C expertise to own memory management and crash debugging. ## The responsibilities FFI hands you - **Memory**: native memory allocated from Dart is not garbage-collected; each allocation needs an owner that frees it, and buffers the C library allocates must go back to the library's own free function. - **Blocking**: the call occupies the calling thread until C returns. The `package_ffi` template's comments say it plainly: short functions can be called on the main isolate, long-running ones block Dart execution and drop frames in Flutter apps, so they belong on another isolate. - **Correctness of signatures**: if the Dart declaration disagrees with the C header (an `Int32` where C has `int64_t`, a missing parameter), the result is undefined behaviour, often a crash, not a Dart exception. Generating bindings from the header with `package:ffigen` removes most of that risk. - **Packaging**: the native library has to be compiled for every target ABI and bundled; build hooks automate this for new packages.
- Can dart:ffi call Kotlin or Swift code directly?Not as such: `dart:ffi` speaks the C ABI. Kotlin or Swift code is reached through platform channels, through code exported with C linkage, or through the separate Java and Objective-C interop generators built on FFI. For ordinary platform SDK calls, a channel or Pigeon is the usual choice.
- What happens if the Dart signature does not match the C function?Nothing checks it at runtime: arguments are placed in registers and on the stack according to the declared types, so a mismatch reads garbage, corrupts memory or crashes the process. That is why generated bindings from the C header are preferred for anything beyond a few functions.
A MethodChannel is like posting a letter to another office and waiting for a reply; dart:ffi is like walking into the next room and using the machine yourself: much faster, but you clean up after yourself and nobody catches you if you press the wrong button.
saying these in an interview costs you the question
- Says FFI calls are asynchronous and return Futures
- Believes dart:ffi works in Flutter web builds
- Thinks the garbage collector frees memory allocated with calloc
- Uses FFI to call a Kotlin class method directly
- Expects a wrong FFI signature to throw a Dart exception