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.

5 (6) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

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:

Create weather.c file.

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:

  1. For Android, you call DynamicLibrary to find and open the shared library libweather.so. You don’t need to do this in iOS since all linked symbols map when an app runs.
  2. 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.
  3. TemperatureFunction defines a native function that accepts no arguments and returns a native C double.
  4. The lookup function is bound to the equivalent Dart function that returns a Dart double.
  5. Assign getTemperature() the returned value from the returned double.

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.

Screen shot of failed to load error.

Oops, that doesn’t look pretty. But don’t worry! You’ll fix that soon.

Note: While the iOS version gives a similar error screen, the error message is slightly different. The iOS version says “Invalid argument(s): Failed to lookup symbol (dlsym(RTLD_DEFAULT, get_temperature): symbol not found)“. This difference is due to how each platform handles dynamic libraries.

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.

Note: If you didn’t setup the prerequisites please refer to the Getting Started section.

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:

First screenshot of Xcode compile settings.

Here’s a step-by-step breakdown:

  1. Select Runner under the left-most icon in the top-left bar.
  2. Under Targets select Runner.
  3. On the row of tabs, select Build Phases.
  4. Expand Compile Sources tab and click the +.

Second screenshot of Xcode compile settings.

Next:

  1. On the popup window, click Add Other…

Third screenshot of Xcode compile settings.

Finally:

  1. Navigate to your project’s src folder and select weather.c.
  2. Click Open.

Return to your Flutter IDE.

Build and run. Then tap Temperature and you’ll see a popup message showing the temperature:

Screen shot of successful 86.0 temperature data retrieved.

Great job! You successfully:

  1. Wrote a C function.
  2. Compiled this function into a shared object and linked it into your Flutter app.
  3. Used FFI to locate the library and function signature.
  4. Bound the native function to a Dart function.
  5. 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.