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?
Binding Functions That Return Pointers
The double your C function returns is a stack-allocated primitive that freely passes between C and Dart code without any memory concerns.
What if you want to obtain a Dart String
from your C function? The C standard library has no concept of a string, so you can only work with NULL-terminated char
array pointers.
Proper Scope
You might feel tempted to add the following to weather.c, but don’t:
char* get_forecast() {
char* forecast = "sunny";
return forecast;
}
This code will compile unless you’ve turned on the right compiler warnings, but it isn’t valid C!
Why? Because you created a stack-allocated char
array, which is only valid within the scope of this function. Once you return the pointer to your Dart code, this no longer points to a valid char
array in memory.
To safely return a char
pointer, you must return a pointer to properly allocated memory.
Add the following code to weather.c:
char* get_forecast() {
char* forecast = "sunny";
char* forecast_m = malloc(strlen(forecast));
strcpy(forecast_m, forecast);
return forecast_m;
}
This function creates a local char pointer for the string "sunny"
, allocates memory for a char pointer memory on the heap of the same size, and copies the contents of the former into the latter.
Then add the following C library include
s to the top of weather.c:
#include <string.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdlib.h>
This code includes header files from the C standard library so that you can use strcpy
, strlen
and a few other functions and types you’ll see later in this tutorial.
In this tutorial, you know exactly what memory you’re copying, so there’s no need to worry.
strcpy
and strlen
because you can inadvertently introduce security holes by copying untrusted data.
In this tutorial, you know exactly what memory you’re copying, so there’s no need to worry.
If you’re a seasoned C developer, you already know that returning pointers to locally-scoped variables is a big no-no. At best, it’ll create incorrect return values. At worst, your app will segfault and crash.
If this is your first time working with C, it’s worth reiterating: don’t return pointers to locally-scoped variables!
Next, you need to create a Dart type to represent a function that accepts no arguments with a return value of type Pointer
.
Typing Dart Functions That Return Pointers
On the Dart side, create the matching typedefs
in ffi_bridge.dart. Locate // TODO: Add new typedef declarations here
and replace it with:
typedef ForecastFunction = Pointer<Utf8> Function();
typedef ForecastFunctionDart = Pointer<Utf8> Function();
Dart FFI uses Pointer<Utf8>
to represent a char
pointer. Keep in mind, the Dart typedef
doesn’t return a String
because you need to manually free the returned pointer. In contrast, TemperatureFunction
directly returns a Dart double
.
Add Functions and Their Respective Lookups
Find and replace // TODO: Add _getForecast declaration here
with:
ForecastFunctionDart _getForecast;
Here you added a _getForecast
of type ForecastFunctionDart
to your FFIBridge
, which is the Dart function that acts as a bridge to a C function.
Next, replace // TODO: Assign value to _getForecast
with:
_getForecast = dl
.lookupFunction<ForecastFunction, ForecastFunctionDart>('get_forecast');
This uses DynamicLibrary
to locate the C function you’ll bridge to, using the name get_forecast
and the typedef ForecastFunction
.
Find // TODO: Add getForecast() here
and replace it with:
String getForecast() {
final ptr = _getForecast();
final forecast = ptr.toDartString();
calloc.free(ptr);
return forecast;
}
In this code, you create a getForecast
that invokes the bound Dart function, converts the returned char pointer to a Dart string and frees the memory allocated for the returned pointer.
Return to main.dart and locate // TODO: Add code to invoke newly created forecast method
replace it and throw UnimplementedError();
with:
_show(_ffiBridge.getForecast());
This invokes the getForecast()
you added in the last step.
Build and run. Then click Today’s forecast.
Voila! Here you:
- Obtain a native
char
pointer. - Convert the pointer to a UTF8/Dart
String
. - Free the allocated memory.
- Pass the String back to your Flutter widget.
Both the get_temperature
and get_forecast
returned primitive types, a double
, and a char
pointer respectively. Neither of these functions accepted any arguments.
Next, you’ll see how to invoke a C function that accepts some arguments. You’ll also see how to return a more complicated data structure, not just a simple pointer.
Arguments and Structs
In this section, you’ll see how to pass arguments from Dart to C by creating a function to return a three-day forecast in either Celsius or Fahrenheit.
Creating A Three-Day Forecast Structure
A three-day forecast needs three temperature values, so you obviously can’t return a solitary double
. You need to create an appropriate struct
.
Add the following to the bottom of weather.c:
struct ThreeDayForecast {
double today;
double tomorrow;
double day_after;
};
Then add a function that converts between Fahrenheit and Celsius:
double fahrenheit_to_celsius(double temperature) {
return (5.0f / 9.0f) * (temperature - 32);
}
In the next section, you’ll write the function to create an instance of the ThreeDayForecast
struct. This function will then populate the struct’s values with the temperature forecast in either Celsius or Fahrenheit.
Accepting Arguments And Returning Structs
Before your app can provide a three-day forecast in both Fahrenheit and Celsius, you need to create a function that accepts arguments.
At the bottom of weather.c, add:
// 1
struct ThreeDayForecast get_three_day_forecast(bool useCelsius) {
// 2
struct ThreeDayForecast forecast;
forecast.today = 87.0f;
forecast.tomorrow = 88.0f;
forecast.day_after = 89.0f;
// 3
if(useCelsius) {
forecast.today = fahrenheit_to_celsius(forecast.today);
forecast.tomorrow = fahrenheit_to_celsius(forecast.tomorrow);
forecast.day_after = fahrenheit_to_celsius(forecast.day_after);
}
// 4
return forecast;
}
Going through step-by-step, this function:
- Accepts a
bool
indicating whether to return Celsius or Fahrenheit values. - Instantiates a struct with some very boring and static values, representing the forecasted temperature over the next three days.
- Converts these values to Celsius if
useCelsius
is true. - Returns the struct.
Since this function returns a struct, you can’t use the same approach as getForecast()
and return a Pointer
. You need to create a matching class on the Dart side to receive the values in this struct.