Calling Native Libraries in Flutter with Dart FFI
In this tutorial, you’ll learn how to use Dart FFI to access native libraries that support C-interoperability. By Nick Fisher.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Calling Native Libraries in Flutter with Dart FFI
30 mins
- 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?
Your First Native Function
First, you’ll create a simple C-sharable object for Dart FFI to access. Then you’ll learn to bind and trigger it from a Flutter widget.
Writing A Simple C Function
You need to create a C function before the Temperature button can invoke it.
Navigate to your project’s root directory and create a folder called src. Add a file called weather.c which has a single function:
Add the following to weather.c:
double get_temperature()
{
return 86.0f;
}
This basic function returns a single double-precision floating-point primitive. WeatherFFI gives you a forecast, but that doesn’t mean it’s a good forecast! :]
With that in place, it’s time to add binding.
Binding: Building A Bridge
Assume your native function compiles to a shared library called libweather.so
and correctly links in your Flutter app. Your Flutter app needs to know where to find this function and how to invoke it.
In lib, create a Dart file called ffi_bridge.dart and add:
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
typedef TemperatureFunction = Double Function();
typedef TemperatureFunctionDart = double Function();
// TODO: Add new typedef declarations here
// TODO: Add ThreeDayForecast here
class FFIBridge {
TemperatureFunctionDart _getTemperature;
// TODO: Add _getForecast declaration here
// TODO: Add _getThreeDayForecast here
FFIBridge() {
// 1
final dl = Platform.isAndroid
? DynamicLibrary.open('libweather.so')
: DynamicLibrary.process();
_getTemperature = dl
// 2
.lookupFunction<
// 3
TemperatureFunction,
// 4
TemperatureFunctionDart>('get_temperature');
// TODO: Assign value to _getForecast
// TODO: Assign value to _getThreeDayForecast here
}
// 5
double getTemperature() => _getTemperature();
// TODO: Add getForecast() here
// TODO: Add getThreeDayForecast() here
}
That’s a lot of code. Here’s a breakdown:
- For Android, you call
DynamicLibrary
to find and open the shared librarylibweather.so
. You don’t need to do this in iOS since all linked symbols map when an app runs. - Then you locate the correct function by specifying its native type signature and name. You use this information to bind to a Dart function with a specific type signature.
-
TemperatureFunction
defines a native function that accepts no arguments and returns a native C double. - The lookup function is bound to the equivalent Dart function that returns a Dart
double
. - Assign
getTemperature()
the returned value from the returneddouble
.
All the appropriate trampolining, or moving between Dart and native code, and binding, or converting native and Dart types, happens in the background.
Next, you’ll see how to trigger this function from Flutter.
Triggering From Flutter
To use your new method, add the following import at the top of main.dart:
import 'ffi_bridge.dart';
Next, find _MyHomePageState
and at the top add:
final FFIBridge _ffiBridge = FFIBridge();
Locate // TODO: Add code to invoke newly created temperature method
and replace it and the throw
line beneath it with:
_show(_ffiBridge.getTemperature());
You’ve now updated the first button to invoke your newly created method.
Save your changes. Build and run.
Oops, that doesn’t look pretty. But don’t worry! You’ll fix that soon.
You told Dart FFI to look for a particular library function, but that library doesn’t exist yet. You haven’t compiled weather.c or linked it to your Flutter app, so that’s next on your agenda.
Building Native Code
Unlike Dart, native C is platform-specific, so you need to configure the compilers for each platform to handle compiling weather.c. Configuring Android Studio and Xcode for compiling native code is a broad topic beyond this tutorial’s scope.
The following two sections will give you enough information to build the shared object used in this example successfully. Thankfully, adding a build step to compile and link C code is painless for both platforms. For more information on building and compiling native Android and iOS apps, please see our Android and iOS tutorials.
Configuring the Android Studio C Compiler
If you followed the tutorial prerequisites, CMake is available as part of the Android NDK. It’s the easiest method for compiling native code during Android builds.
Open android/app/build.gradle. Locate // TODO: Add externalNativeBuild here
and replace it with:
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
This tells the Android build system to call CMake with CMakeLists.txt when building the app.
Next, still in the android/app directory, create the file named CMakeLists.txt:
cmake_minimum_required(VERSION 3.4.1)
add_library(
weather
SHARED
../../src/weather.c
)
If you’re not familiar with CMake, this says, “compile weather.c to a shared object library called libweather.so“.
In the next section, you’ll configure the Xcode C Compiler. Skip this section if you’re not using macOS or you’re not building the project for an iOS device or simulator.
Configuring the Xcode C Compiler
In your IDE’s terminal window, run flutter build ios
. You’ll receive confirmation the build was successful and return to the prompt.
Open Xcode and open your starter project’s ios/Runner.xcworkspace.
Using the following screenshot as reference, make the following updates:
Here’s a step-by-step breakdown:
- Select Runner under the left-most icon in the top-left bar.
- Under Targets select Runner.
- On the row of tabs, select Build Phases.
- Expand Compile Sources tab and click the +.
Next:
- On the popup window, click Add Other…
Finally:
- Navigate to your project’s src folder and select weather.c.
- Click Open.
Return to your Flutter IDE.
Build and run. Then tap Temperature and you’ll see a popup message showing the temperature:
Great job! You successfully:
- Wrote a C function.
- Compiled this function into a shared object and linked it into your Flutter app.
- Used FFI to locate the library and function signature.
- Bound the native function to a Dart function.
- Invoked the Dart function to return a double from the C function.
You also know that it’s a beautiful 86°F, or 30°C, degrees outside. What a lovely day!
Now it’s time to bind a function that returns a pointer.