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.
$ 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.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.
// 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.
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.
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.
# 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.
# 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.
lipo \
-create \
libsum_arm64_iphonesimulator.a \
libsum_amd64_iphonesimulator.a \
-output libsum_iphonesimulator.a
Now we can create the xcframework
using the below command.
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.
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.
void enforce_binding();
The EnforceBinding.swift
file will have the below code.
public func dummyMethodToEnforceBundling() {
enforce_binding() // disable tree shaking
}
Now, we can use the libsum
library in the Dart code using FFI.
// 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.
# 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.
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.
// 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.
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.
Top comments (3)
fyi: you can use dynamic libraries using the jni and method channels.
it is a similar process for ios, no need for an intermediary layer like ffi
How the src/libsum.h file will be generated.
With the go build command