Meet the coolest tech marriage: Flutter & Go!
As a trendy developer, you may have heard of Flutter, the mighty framework to build native like mobile, web and desktop apps. After using it for 9 months now, I’m confident to say that it’s here to stay.
Flutter
Its main features:
- Dart language. A typed alternative to Javascript/Typescript
- Declarative UI built into the language
- Tons of UI widgets supported by Google
- A package manager with plugins that (unlike RN) won’t break your build
- Stateful hot reload
- Work in progress support for web and desktop
However, if you come from React Native there’s a good chance that the NPM packages that made your life easier do not have a Dart version.
In such scenario, you have a few options:
- Rewrite them yourself
Rewriting JS into Dart may be time-consuming for large dependencies. If you are on a deadline or can’t afford to maintain the ported package, this might not be your best option.
- Find a way to run Javascript within a Flutter app
I have made several attempts to run a “headless” web view within Flutter and enqueue requests by sending messages back and forth. However, the approach would not be solid enough and hidden web views would lead to unintended side effects. Great for use in a hackathon, but foolish to ship in production.
- Find native implementations
What if the functionality you need is on Cocoa Pods or Maven? Well, jumping into Objective C and Java is fine, but this would defeat the main advantage of Flutter, forcing us to write things twice…
But hey, what if we don’t have to?
Golang to the rescue
The Go language is intended for systems programming and 10 years after it was announced, its package ecosystem is quite healthy. The downside for us is that it is designed to build Linux, Unix, macOS and Windows binaries.
Luckily, a few years ago Google released a precious package that I didn’t hear about until recently: Go Mobile.
Go Mobile lets you build entire applications as well as native libraries that can be imported on XCode or Android Studio. Doesn’t it sound great?
The plan
Our goal is to develop mobile apps using the Flutter tools we know and love. Instead of embedding any libraries manually on our project, we will keep things modular by creating a plugin, writing once in Dart, reusing Go packages and importing our plugin wherever we need it.
Our plugin will contain:
- The compiled Go bindings
- A Dart module so we can export our methods
- iOS and Android projects, with simple glue code to forward requests from Dart to Go
- An example project to test the plugin features right away
Get started
Make sure you have XCode, Android Studio (with NDK, CMake and LLDB), Go, GoMobile, Cocoa Pods and Flutter installed.
Let’s create the plugin:
$ flutter create --org com.mylib --template=plugin mylib
This will scaffold a starter Flutter plugin on your computer named mylib
. Make sure that the name of your plugin is not the same as any Go dependency, since this would cause naming collisions on XCode.
If we run cd example
and flutter run
, we will get an already functional dummy plugin invoking Swift code:
Go library
Next, let’s fetch the Go library we want to use:
$ export GOPATH=~/go
$ go get github.com/divan/num2words
On ~/go/src/github.com/divan/num2words/num2words.go
you should see the exported function Convert
, which accepts an number and returns it as text.
Go bindings
Let’s compile this into iOS and Android libraries.
On the plugin folder:
$ mkdir ios/Frameworks
$ cd ios/Frameworks
$ gomobile bind -target ios github.com/divan/num2words
You should see Num2words.framework
on <plugin-folder>/ios/Frameworks
. Easy, right? Now the same for Android:
$ mkdir android/libs
$ cd android/libs
$ gomobile bind -target android github.com/divan/num2words
Your should now see num2words.aar
and num2words-sources.jar
on <plugin-folder>/android/libs
.
We are done with Go!
Importing the bindings on iOS
Now, let’s tell XCode and Gradle to use the bindings.
For iOS, edit ./ios/mylib.podspec
and append the vendored_frameworks
line near the end:
This will trigger a pod install
, copying the framework file when we use the Flutter plugin in the future.
Now open ios/Classes/SwiftMylibPlugin.swift
, import the bindings library and adapt the handle
function to accept our new convert
request:
call.method
contains a string with the name of the operation we are requesting, while call.arguments
has a generic type that we will need to check and cast on runtime.
Note how gomobile
has renamed the Convert
function into Num2wordsConvert
.
Importing the bindings on Android
Doing the same for Android is no different.
Edit android/build.gradle
and add the following line within the dependencies
directive:
This will import any *.aar
file that we place on the android/libs
folder.
Next, let’s handle our requests to execute Convert
. First, import the package of the Go library:
And next, add an if
statement to capture our new requests:
Note how in this case, we need to reference the Go function as Num2words.convert
(lowercase). Not sure why names can’t be kept as they were.
First build
If we head back to the example
folder, run flutter build ios
, flutter build apk
and get no warnings, this means that we compiled our first Flutter Go plugin!
Implementing the plugin logic
Our native part is now ready. Let’s send requests to it and see how it works.
Edit lib/mylib.dart
so that our Flutter plugin exposes a Dart method to the outer world:
Now, every time we import the Flutter plugin and call num2String(...)
, Flutter will do the heavy lifting for us and translate our call into a message for Swift/Java for which we just implemented a handler.
We can pass up to one argument, and such argument has to be a boolean, integer, string, List or Map. The Lists and Map values should be booleans, integers or strings. Or we could simply pass custom JSON data serialized into a string.
Running our plugin
Finally, we are ready to use our shiny Flutter plugin!
Edit example/lib/main.dart
to make use of it:
- We import the Dart package of our plugin
- We call the Go method on the native side when the Widget initializes and update the Widget state
- We render the result
Finally, open the iPhone and Android simulators and run:
$ flutter run -d all
Wohooo! 🎉🎊
Yes, we can
On more real-life projects, what could we do with that?
By using Go, we get access to a wide ecosystem of libraries for free. They can be great if you need to do:
- Image/video processing
- Media transcoding
- Using math libraries
- Write once, use everywhere
- Run heavy operations where smartphones would have a hard time with interpreted languages
Considerations:
- If your Go methods are renamed and you can’t work out their name on iOS, you can have a look at the generated header file
ios/Frameworks/Num2words.framework/Headers/Num2words.objc.h
- On Android, you can explore within the
android/libs/num2words-sources.jar
package with Android Studio
Current limitations:
- As mentioned, the Go methods exported for iOS/Android may not pass data beyond integers, booleans, strings, maps or lists. However, these methods can pass any type of data they need with the rest of the library.
- Since mobile and desktop operating systems don’t have equal capabilities, it’s possible that very specific API’s refuse to compile. Don’t expect to compile a library to send signals to other processes or fetch the process list on iOS.
If you liked what you found, please consider clapping 👏 out loud, smiling 😊, loving ❤️ and spreading the word 📤 to anyone who might be happy to read it too.
Until the next time!