James Williams
BlueskyLinkedInMastodonGithub

Native Interop on Flutter, Part III

Tags: flutter

Over the past two posts, I created a Flutter native interop plugin twice. Once using bare metal invocation through jni/ffi and again using pigeon.

The direct interop method gave use fine grained control at the cost of a lot of code generation blog and somewhat mangled Dart functions. It’s important to say that isn’t Dart’s fault, it just doesn’t natively implement some of the things available in other langauges. It was a bit overpowered for the small amount of data transmitted in a permissions request which is nowhere near being either a large amount of data or data intolerant to latency caused by using method channels.

Pigeon addressed some of the drawbacks by enforcing a typesafe interface in the baseline implementation and going further with the stable JNI/FFI version that potentially cuts down on the latency from message passing.

Either method can be made to function with enough effort but the "right" method depends on your overriding goals and requirements.

When to Use Which Native Interop Method?

A guide for choosing between direct native invocation (JNI/FFI) and either Pigeon variant. This matrix assumes you have already exhausted simpler Dart-only solutions.

Goal / Constraint Best Tool Why? Notes
Pure Dart (No Flutter)jnigen/ffigen Direct access to Java/Kotlin classes or C-ABI memory management. No Flutter runtime dependency required. High boilerplate cost; requires deep native knowledge. Always the most complex path.
Cross-Platform Safety & Simplicity Pigeon (Default) Enforces type safety across platforms via structured messaging protocols, drastically reducing common runtime errors. Recommended baseline for most plugins. Use when data size is moderate and latency tolerance is high. Use ffi/jni versions where available.
Complex C-API / Manual Memory Control jnigen/ffigen When you must interact with a complex, existing C-ABI or require manual memory management for performance reasons. The absolute fallback when any abstraction layer fails due to hardware/OS constraints.
High-Throughput Data Transfer / Low Latency Focus jnigen/ffigen Bypasses serialization overhead of message passing entirely, ideal for massive data structures (e.g., raw image buffers). Only profile and use this if performance proves pigeon's messaging layer is the actual bottleneck.

My answer today is very different from what it was in 2025. I believe Pigeon should be your baseline for Flutter apps and you should only drop down to jni/ffi when it is absolutely necessary.