How to use GoLang in Flutter Application - Golang FFI
Tags: flutter, go, ffi, dart
Published: 2023-12-20 02:35:14

---
title: How to use GoLang in Flutter Application - Golang FFI
published: true
description: 
tags: #flutter #golang #ffi #dart
---

GoLang is a very powerful language that is easy to use. Writing a GoLang program and compiling it to a binary file is very easy. Golang is very useful in writing high-performance and low-latency applications, but it needs improvement in GUI. Flutter is a compelling cross-platform UI framework. It allows you to write a beautiful application effortlessly. Mixing GoLang and Flutter is an excellent idea to write a high-performance and beautiful application.

In this article, we will see how to compile a GoLang-based library and create a Flutter plugin package for a Flutter application.

## What is FFI?

FFI stands for Foreign Function Interface. It is a way to call functions written in other languages from Dart. FFI helps use libraries written in other languages in a Flutter application. Flutter supports FFI for Android, iOS, Linux, macOS, and Windows. In this article, we will see how to use FFI.

In Dart, the programming language used to write Flutter applications, we can use the `dart:ffi` package to use FFI. The `dart:ffi` package is used to call functions written in other languages from Dart. We can use the `dart:ffi` package to call functions written in C, C++, or Rust. In this article, we will see how to use the `dart:ffi` package to call functions in GoLang.

## Create a plugin project for Flutter

The Flutter plugin can contain Dart and platform-specific code written in Kotlin, Swift, Objective-C, or GoLang. We can use a Flutter plugin to add functionality to a Flutter application.

The below command will create a Flutter plugin project named `native_add`. The `native_add` project will support Android, iOS, Linux, macOS, and Windows. The `native_add` project will have a `lib` folder that contains the Dart code and an `example` folder that contains a Flutter application to test the plugin.
```bash
 $ flutter create --platforms=android,ios,macos,windows,linux --template=plugin_ffi native_add
 $ cd native_add
```

## Create a GoLang library

The plugin project we created in the previous step will have a `src` folder. The `src` folder will have a `cpp` folder that contains the C++ code. We will create a GoLang library in the `src` folder. The GoLang library will have a function to add two numbers.

First, we will create a `go.mod` file. The `go.mod` file defines the module name and the GoLang version. The `go.mod` file is used to manage dependencies. We will not use any dependencies in this article.

```go
// go.mod file
module go_code

go 1.21.3
```

Second, we will create a `sum.go` file. The `sum.go` file will have a function to add two numbers. The `sum.go` file will also have a function called `enforce_binding`. The `enforce_binding` function enforces the binding in the ios application. The Dart code will not use the `enforce_binding` function, but it was the trick I found to make the library linked correctly for iOS.

Above each function we want to expose, we will add a comment `//export <function_name>`. The `//export <function_name>` comment exposes the function to other languages. We must also use the type `C.int` instead of `int` in the function signature. The `C.int` type is used to make the function compatible with other languages.

```go
// sum.go file
package main

import "C"

//export sum
func sum(a C.int, b C.int) C.int {
  return a + b
}

//export enforce_binding
func enforce_binding() {}

func main() {}
```

### Compile the GoLang library for Android

Since we are compiling the GoLang library for Android, we must use cross-compilation. Modern Android devices mostly use `arm64` or `x86_64` architectures. We will compile the GoLang library for both `arm64` and `x86_64` architectures.

```bash
export ANDROID_OUT=../android/src/main/jniLibs
export ANDROID_SDK=$(HOME)/Library/Android/sdk
export NDK_BIN=$(ANDROID_SDK)/ndk/23.1.7779620/toolchains/llvm/prebuilt/darwin-x86_64/bin

# Compile for x86_64 architecture and place the binary file in the android/src/main/jniLibs/x86_64 folder
CGO_ENABLED=1 \
GOOS=android \
GOARCH=amd64 \
CC=$(NDK_BIN)/x86_64-linux-android21-clang \
go build -buildmode=c-shared -o $(ANDROID_OUT)/x86_64/libsum.so .

# Compile for arm64 architecture and place the binary file in the android/src/main/jniLibs/arm64-v8a folder
CGO_ENABLED=1 \
GOOS=android \
GOARCH=arm64 \
CC=$(NDK_BIN)/aarch64-linux-android21-clang \
go build -buildmode=c-shared -o $(ANDROID_OUT)/arm64-v8a/libsum.so .
```

Now we can use the `libsum` library in the Dart code using FFI.
```dart
DynamicLibrary.open('libsum.so');
```

### Compile the GoLang library for iOS

We need some extra steps to cross-compile the Golang library and use it for iOS. iOS only supports static libraries. So, we need to compile the GoLang library as a static library. We also need to modify the podspec file to use the static library in the Flutter plugin. Another thing to note is that we need to create `xcframework` for iOS. The `xcframework` is used to support both simulator and device architectures.

```bash
# build_ios.sh file
#!/bin/sh
export GOOS=ios
export CGO_ENABLED=1
# export CGO_CFLAGS="-fembed-bitcode"
# export MIN_VERSION=15

SDK_PATH=$(xcrun --sdk "$SDK" --show-sdk-path)

if [ "$GOARCH" = "amd64" ]; then
    CARCH="x86_64"
elif [ "$GOARCH" = "arm64" ]; then
    CARCH="arm64"
fi

if [ "$SDK" = "iphoneos" ]; then
  export TARGET="$CARCH-apple-ios$MIN_VERSION"
elif [ "$SDK" = "iphonesimulator" ]; then
  export TARGET="$CARCH-apple-ios$MIN_VERSION-simulator"
fi

CLANG=$(xcrun --sdk "$SDK" --find clang)
CC="$CLANG -target $TARGET -isysroot $SDK_PATH $@"
export CC

go build -trimpath -buildmode=c-archive -o ${LIB_NAME}_${GOARCH}_${SDK}.a
```

The above script will create a static library for iOS. We must use the script to build the static library for both simulator and device architectures. 

```bash

# Compile for x86_64 simulator architecture
GOARCH=amd64 \
SDK=iphonesimulator \
LIB_NAME=libsum \
./build_ios.sh

# Compile for arm64 simulator architecture
GOARCH=arm64 \
SDK=iphonesimulator \
LIB_NAME=libsum \
./build_ios.sh

# Compile for arm64 device architecture
GOARCH=arm64 \
SDK=iphoneos \
LIB_NAME=libsum \
./build_ios.sh
```

The above script will create three static libraries. Before creating the `xcframework`, we must combine the two static libraries for the simulator architecture. The `lipo` command can combine the two static libraries.

```bash
lipo \
-create \
libsum_arm64_iphonesimulator.a \
libsum_amd64_iphonesimulator.a \
-output libsum_iphonesimulator.a
```

Now we can create the `xcframework` using the below command.

```bash
xcodebuild -create-xcframework \
-output ../ios/libsum.xcframework \
-library ios-arm64/libsum.a \
-headers ios-arm64/libsum.h \
-library ios-simulator/libsum.a \
-headers ios-simulator/libsum.h
```

The `libsum.xcframework` will be created in the ios folder.

We need to modify the `podspec` file to use the `xcframework`. We must add the lines below to the `podspec` file under the `ios` folder.

```ruby
s.public_header_files = 'Classes/**/*.h'
s.vendored_frameworks = 'libsum.xcframework'
```

After some trial and error, I found that we need to use the library in the `Classes` folder to link it properly. So, we create `binding.h` and `EnforceBinding.swift` files under the `Classes` folder.

The `binding.h` file will have the below code.
```c
void enforce_binding();
```
The `EnforceBinding.swift` file will have the below code.
```swift
public func dummyMethodToEnforceBundling() {
    enforce_binding() // disable tree shaking
}
```

Now, we can use the `libsum` library in the Dart code using FFI.
```dart
// The libsum library is statically linked in the native_add so we only need to load native_add.
DynamicLibrary.open('$native_add.framework/native_add');
```

## Create a binding in Dart using `ffigen`

The `ffigen` tool generates the Dart code to use the GoLang library. We will create a `ffigen.yaml` file to configure the `ffigen` tool. The `config.yaml` file will have the below code.

```yaml
# Run with `flutter pub run ffigen --config ffigen.yaml`.
name: NativeLibrary
description: Bindings to `src/sum.h`.
output: 'lib/generated_bindings.dart'
headers:
  entry-points:
    - 'src/libsum.h'
preamble: |
  // ignore_for_file: always_specify_types
  // ignore_for_file: camel_case_types
  // ignore_for_file: non_constant_identifier_names
  // ignore_for_file: unused_field
  // ignore_for_file: unused_element
comments:
  style: any
  length: full
```

Now, we can run the `ffigen` tool to generate the Dart code.
```bash
flutter pub run ffigen --config ffigen.yaml
```

The `ffigen` tool will generate the `generated_bindings.dart` file under the `lib` folder based on the `libsum.h` file in `src` folder that was generated by the `go build` command in the previous step.

## Use the GoLang library in Dart

Now, we can use the GoLang library in the Dart code. We can use the `DynamicLibrary.open` method to load the library. We can use the `NativeLibrary` class to call the functions in the GoLang library.

```dart
// lib/native_add.dart
import 'dart:ffi';
import 'dart:io';

import 'generated_bindings.dart';

int sum(int a, int b) => _bindings.sum(a, b);

const String _libName = 'native_add';

/// The dynamic library in which the symbols for [NativeAddBindings] can be found.
final DynamicLibrary _dylib = () {
  if (Platform.isMacOS || Platform.isIOS) {
    return DynamicLibrary.open('$_libName.framework/$_libName');
  }
  if (Platform.isAndroid || Platform.isLinux) {
    return DynamicLibrary.open('libsum.so');
  }
  if (Platform.isWindows) {
    return DynamicLibrary.open('$_libName.dll');
  }
  throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
}();

/// The bindings to the native functions in [_dylib].
final NativeLibrary _bindings = NativeLibrary(_dylib);
```

Now, we can use the `sum` function in the Dart code from the Flutter project that uses this plugin.
```dart
import 'package:native_add/native_add.dart' as native_add;
native_add.sum(1, 2);
```

## Conclusion

In this article, we checked how to use GoLang in a Flutter application. We created a GoLang library and compiled it for Android and iOS. We also created a Flutter plugin to use the GoLang library in a Flutter application. We used the `ffigen` tool to generate the Dart code to use the GoLang library. We also checked how to use the GoLang library in the Dart code.

I also created a sample project to demonstrate using GoLang in a Flutter application. You can find the sample project in the below link. The example also contains the script to compile the GoLang library for MacOS. 
https://github.com/leehack/flutter_golang_ffi_example
It's still missing for Windows and Linux but it should be much easier than for Android and iOS. Like Android or iOS, we must cross-compile and link the GoLang library for Windows and Linux in the CMake file.

Due to the lack of information, I had to spend a lot of time figuring out how to use GoLang in a Flutter application. This article will help you to use GoLang in a Flutter application.