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?
Receiving a Struct in Dart
Receiving a struct data type means you need to create a Dart class that mimics the layout and naming of this struct, allowing Dart FFI to easily bridge between the native C type and the Dart type.
In ffi_bridge.dart, locate // TODO: Add ThreeDayForecast here
and replace it with:
class ThreeDayForecast extends Struct {
// 1
@Double()
external double get today;
external set today(double value);
@Double()
external double get tomorrow;
external set tomorrow(double value);
@Double()
external double get day_after;
external set day_after(double value);
// 2
@override
String toString() {
return 'Today : ${today.toStringAsFixed(1)}\n'
'Tomorrow : ${tomorrow.toStringAsFixed(1)}\n'
'Day After ${day_after.toStringAsFixed(1)}';
}
}
// 3
typedef ThreeDayForecastFunction = ThreeDayForecast Function(Uint8 useCelsius);
typedef ThreeDayForecastFunctionDart = ThreeDayForecast Function(
int useCelsius);
Here:
- The
Double()
annotation indicates the native type of each field to Dart FFI. - You return the forecast listed to one decimal point.
- The
typedef
indicates the method will have a return type of this class and expects a singleint
(Dart) /Uint8
C argument. There’s no matching FFI Boolean type, so you use an unsigned 8-bit integer instead.
get_three_day_forecast
returns the ThreeDayForecast
struct by value, not by reference. To review the difference between pass-by-value and pass-by-reference, check out this article.
You created a Dart class that can receive the values in the C ThreeDayForecast
struct. Now, you can add the code needed to invoke the get_three_day_forecast
function.
Binding the Native Struct to a Dart Class
The last feature you’ll add is getting the three-day forecast.
Still in ffi_bridge.dart, locate and replace // TODO: Add _getThreeDayForecast here
with:
ThreeDayForecastFunctionDart _getThreeDayForecast;
Here you added a _getThreeDayForecast
property to your FFIBridge
class, representing the Dart function that will bridge to a C function.
Find // TODO: Assign value to _getThreeDayForecast here
and replace it with:
_getThreeDayForecast = dl.lookupFunction<ThreeDayForecastFunction,
ThreeDayForecastFunctionDart>('get_three_day_forecast');
This uses DynamicLibrary to look up the C function by name get_three_day_forecast
with the arguments and return type specified by the ThreeDayForecastFunction
typedef.
Fially, you need to return the three-day forecast. You guessed it, replace // TODO: Add getThreeDayForecast() here
with:
ThreeDayForecast getThreeDayForecast(bool useCelsius) {
return _getThreeDayForecast(useCelsius ? 1 : 0);
}
In this code, you added a getThreeDayForecast
method to call this function and return an instance of the Dart class ThreeDayForecast
that contains the result.
Finally, in main.dart change // TODO: Add code to invoke newly created 3-day forecast (Fahrenheit) method
and the throw
to call this method when the user presses the relevant button:
_show(_ffiBridge.getThreeDayForecast(false));
Do the same for // TODO: Add code to invoke newly created 3-day forecast (Celsius)
and throw
:
_show(_ffiBridge.getThreeDayForecast(true));
This last update removed the last error shown on the Dart Analysis tab. :]
Build and run. Tap 3-day forecast (Fahrenheit):
Tap anywhere on the barrier to dismiss the dialog. Then tap 3-day forecast (Celsius):
Looking good! You passed a bool
argument to, and returned a struct from, your native function.
With that, WeatherFFI is complete! It looks like you’ll need some lotion and sunglasses for the next few days.
Admittedly, the forecast will never change, so don’t rely on it if you’re deciding whether to take an umbrella tomorrow.
But in terms of learning how to call native code directly from Dart, it’s a resounding success. You now know how to compile, declare, locate and invoke a native function from within a Flutter app on both iOS and Android.
Now that you understand how to use Dart FFI to invoke native code, take a look at when you might want to use FFI and when you might want to use platform channels.
Platform Channels vs Dart FFI
You’ve already seen a few use cases for platform channels compared with FFI.
In general, use platform channels for device or OS-specific functionality like cameras, notifications and integrations with other apps. Android, iOS, desktop and web apps all expose different APIs for taking advantage of that functionality.
Use FFI if you’re writing a Flutter app that needs third-party, platform-agnostic libraries. Examples include machine learning libraries, rendering engines, cryptography and signal processing.
While it’s technically possible to use FFI for your own standalone native code, it’s more likely you’ll need FFI to integrate libraries like OpenCV, Tensorflow or PyTorch into your Flutter app.
Where to Go From Here?
Download the completed project files by clicking Download Materials at the top or bottom of the tutorial.
To avoid tedious boilerplate typedef code, check out the official ffigen repository, or browse some more FFI samples at the official Dart ffi repository. If you’re interested in understanding FFI in a bit more detail, read the official Flutter documentation on implementing Dart FFI.
If you want to use FFI to bind to a game engine, check out How to Create a 2D Snake Game in Flutter for some ideas about where to draw the line between your Flutter code and your native library.
FFI can be tricky, so please join the forum discussion below if you have any questions or run into any issues. In the meantime, you also might want to check the actual weather report for your area to minimize the risk of wearing Hawaiian shirts when it’s sub-zero and snowing!