skip to content

In Flutter, what does dart:ffi let you do, and when would you call a C library through it instead of a MethodChannel?

level: juniorimportance: should knowfreq 35%

answer

  1. C ABI, not Kotlin or Swift
  2. synchronous call on the calling thread
  3. no codec, no message hop
  4. not available on the web
  5. native memory is yours to manage

basics

~20 s

dart: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

for a junior

Recall that dart:ffi calls C functions directly and synchronously, while channels send asynchronous messages to Kotlin or Swift code.

for a middle

Explain the trade-offs: no codec and synchronous results versus manual memory, blocking calls and crash-prone signatures, and the lack of web support.

for a senior

Choose between FFI, channels and generated bindings for a real library, and plan how blocking calls and native memory are contained.

for a principal

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