Calling Native Libraries in Flutter with Dart FFI
- Under the Hood of a Flutter App
- Platform Channels and Plugins
- Native Code
- Bridging Dart and Native Code with FFI
- Getting Started
- Android Setup
- macOS or iOS Setup
- Running WeatherFFI
- Your First Native Function
- Writing A Simple C Function
- Binding: Building A Bridge
- Triggering From Flutter
- Building Native Code
- Configuring the Android Studio C Compiler
- Configuring the Xcode C Compiler
- Binding Functions That Return Pointers
- Proper Scope
- Typing Dart Functions That Return Pointers
- Add Functions and Their Respective Lookups
- Arguments and Structs
- Creating A Three-Day Forecast Structure
- Accepting Arguments And Returning Structs
- Receiving a Struct in Dart
- Binding the Native Struct to a Dart Class
- Platform Channels vs Dart FFI
- Where to Go From Here?
Dart is a great language, but it doesn’t expose all the functionality you might need for a Flutter app. Your app might have performance-critical code that would benefit from being written in a lower-level language like C, C++ or Rust. Or you might need to link a specific external binary shared library, like TensorFlow or PyTorch Mobile.
Foreign function interface, or FFI, involves code written in one language interfacing with another language. Developers have been able to integrate Dart with native code in beta for some time. However, with Dart v2.13, Dart FFI is now available on the stable channel.
That’s great news for Flutter developers! If you’re new to FFI, now is the perfect time to explore taking your Flutter app beyond Dart.
In this tutorial, you’ll learn how to invoke native code by building WeatherFFI, a Flutter app that generates basic, albeit fake, weather data from a native library.
In the process, you’ll learn:
- About FFI and how it lets Dart code invoke code written in other languages.
- How FFI differs from Flutter platform channels.
- To automatically compile and link C code when building a Flutter app.
- How a Flutter app can use FFI to call code written in C.
Before you start, consider what’s going on under the hood of a Flutter app.
Under the Hood of a Flutter App
In a Flutter app, Dart code runs alongside the Flutter framework, calculating widget sizes and drawing to the screen at around 60 frames per second:
Your Dart code can use any of the functionality available in the Dart standard library, like opening a file or network connection.
But the Dart standard library only exposes a small amount of the functionality available to devices or operating systems.
For example, you can’t use Dart code to work directly with the speakers, camera or microphone. Working directly with these devices requires Java/Kotlin code on Android or Objective-C/Swift code on iOS. For the purposes of this tutorial, call this code platform code.
Sometimes, external plugins or packages let you write Dart code to work indirectly with these devices. These plugins or packages expose a Dart interface that you can use to invoke platform code over platform channels.
Platform Channels and Plugins
Dart code can’t interoperate with platform code directly. The Dart compiler can’t use the same types, memory layouts, function signatures and other lower-level conventions your platform code uses.
You need a bridge between the two that takes code Dart understands and translates it to code that Java, Kotlin, Swift or Objective-C understand.
The Flutter authors addressed this with plugins and platform channels, which let messages pass between Dart and platform code.
Most Flutter apps that introduce device-specific functionality use platform channels to bridge Dart and platform code.
Some apps need greater control over memory management and garbage collection. For example, an app that uses 3D game engines such as Unreal Engine or a machine learning library like Tensorflow needs greater control.
Developers generally write these apps in languages like C, C++ or Rust. For the purpose of this tutorial, call this native code.
For a Flutter app written in Dart to use a library written in native code, your app needs some way to bridge the two, just like Flutter platform channels act as a bridge between Dart code and platform code.
However, you can’t use platform channels since they only translate between Dart and platform code such as Java, Kotlin, Swift or Objective-C.
If you want to bridge between Dart code and native code, you need FFI.
Bridging Dart and Native Code with FFI
As mentioned earlier, FFI stands for Foreign Function Interface, a general term for any mechanism that lets developers write code in one programming language to invoke, or bridge, code written in another. It’s not just a Dart concept: many languages expose FFI to interoperate with binary libraries.
Developers write many of these binary libraries in C or C++ and distribute them as pre-compiled shared objects.
In this tutorial, you’ll learn how to do this by writing WeatherFFI, a Flutter app that retrieves some simple weather forecast data from native C code.
This is obviously a contrived example. In the real world, a Flutter app would use FFI for something more like Snapchat’s Face Morph, real-time image recognition or video transcoding with FFmpeg.
But this simple example will show the fundamentals behind FFI and give you the tools to move on to more advanced use cases. With theory out of the way, it’s time to get started!
Download the starter project by clicking Download Materials at the top or bottom of the tutorial.
This tutorial uses VS Code, but all tutorial steps should apply equally to Android Studio.
If you plan on using the Android Emulator or device, follow these steps to ensure Android Studio has both Android Native Development Kit, or NDK, and CMake installed. This applies to macOS, Linux and Windows.
NDK is an add-on to the Android SDK that enables the compilation of native code for Android apps. CMake is a tool that works with Gradle to use a native library.
Once you verify you installed NDK and CMake, you can use either VS Code or Android Studio.
macOS or iOS Setup
If you plan on using the Xcode Simulator or an Apple device, you already have everything you need for this tutorial as long as you followed the official Flutter documentation to install Xcode and configured the Xcode command-line tools.
It’s now time to start building your app.
Open the unzipped folder in your preferred IDE. Either run
flutter pub get in the terminal, or open pubspec.yaml and click the Pub get tab that appears in your IDE. Once complete, open lib/main.dart.
Avoid lines longer than 80 characters. These are from lint rules in analysis_options.yaml. The project will still run and the errors will be addressed as you make updates.
Build and run.
You’ll see four buttons:
- Temperature retrieves the current numeric temperature.
- Today’s forecast retrieves the descriptive forecast for the day.
- 3-day forecast (Fahrenheit) retrieves the 3-day forecast in Fahrenheit.
- 3-day forecast (Celsius) retrieves the 3-day forecast in Celsius.
Right now, these buttons throw an
UnimplementedError() exception when tapped. Soon you’ll hook each one up to a C function via FFI. Once that’s done, you’ll know if tomorrow is a raincoat day or a swimsuit day!
Open pubspec.yaml. In the
dependencies: section, locate
# // TODO: Add ffi package here and replace it with the following, making sure it’s indented the same as
This code lets you use Dart FFI. Now you’ll add your first native function.