React Native allows us to build mobile apps, using Javascript. To me it's the
first platform that actually delivers on that promise; it does this through a mindful
architecture, best-in-class tooling and workflow, and a pragmatic approach for
cross-platform development. It didn't state its purpose is to provide an ability to write 100%
Javascript code for any given project, nor did promise a perfect, single,
cross-platform codebase for your mobile apps. To me, that is the entire
difference.
And so, the idea of perfect cross-platform code sharing and an app that's
built entirely in Javascript is inherently tricky, because:
- Some parts you have to build natively.
- Different platforms are different in experience.
- Different devices perform differently.
- There's no silver bullet.
This means that ultimately we're all bound to write some native code. Writing native code is a
radical shift from our React Native experience: we have to know the tooling more
intimately; Xcode, Android Studio and Gradle and the Android SDK, Obj-C or
Swift, and Java or any Android-friendly JVM language such as Kotlin. We also
need to get familiar with each platform's framework mental model, such as
Android's Intents and Views, and Cocoa's controllers, views and delegates.
Why Go Native?
But before that, even when on React Native, we can list real reasons that force us to ditch Javascript, and
go native.
Performance. This is arguably the first item that crosses everyone's mind
when they say "native code", and it's also the first item you should work
hard to rule out. You have to first prove, through numbers, that you have a performance
problem. And then I'd remain suspicious if I were you. Still, native code
trumps Javascript performance by a good degree for certain workloads.
Low level OS access. This is already all around us. We use React Native
packages that deal with the native parts of each OS, because
core React Native hasn't got to it yet, or it's out of scope.
Infrastructure and robustness. There are tasks that are wasteful to do on the
Javascript engine such as processing images, moving large amounts of bytes
across the React Native bridge, processing files and more.
Tricky async logic. We might prefer using the native OS facilities for
dealing with async workflows, or prefer the "local" language constructs for
doing so. Different mental models for concurrency like CSP and the actor model come to mind.
Libraries and ecosystem. Some libraries might not be available on npm, or don't
have the quality we expect, or features, or community support. The native
variant might be already gold-standard.
Obfuscation and resistance to reverse engineering. React Native Javascript code ships with
your binary, and anyone can grab it from your package. While some platforms such
as Xamarin encrypt and embed it in native code, it's still there (PDF) for an
attacker to pry open. In most cases it isn't a concern but there are plenty of cases
where your IP is actually your code.
Code sharing with a backend. While one of the major benefits of using Node.js
is sharing the backend and frontend Javascript code, it may be that your
backend isn't built on Node.js nor Javascript, and you'd still want to share
artifacts such as data model, validation and business rules.
Lastly, my favorite: binary communication. While you could communicate over a
binary protocol such as Protobuf from your React Native process, it may be
painful and challenging to unpack binary data, or for that to perform well, and
for that to be maintainable or supported by existing platform and
libraries.
As React Native evolves, some of the items in this list will disappear, and
still some, like obfuscation and others, will always remain.
Using Go for Cross-Platform Mobile Development
Ruling out guest VMs that, as Javascript, host a language on
a given platform, or commercial engines like
Apportable, we're left with C and C++ (dropbox
used C++ for sharing non-UI
code)
as target languages for native code sharing.
But these are not safe (as in memory safety), and in comparison to Javascript,
are hard core. Using Java, Obj-C or C/C++ may create a considerable amount of
friction. There's also Rust, but I'm not sure how well it plays with ARM, and
what kind of mobile abstractions it has (none?).
Up until a couple years ago, that could have been where our experiment ended.
Then, Go (the language) has added support to export binaries as C-libraries,
which opened up a world of opportunities. For example, years ago I used
Go to fix performance bottlenecks in Ruby, and later wrote an article and a
reference Gem
implementation to
show how you could do it too.
Another of which was putting Go on mobile devices. I'm going to show you that
Go is as good a language to use for cloud infrastructure, as well as mobile
development, and - to great extent - mobile development for React Native.
Our Prisma Clone: Primer
We'll make an app that render images like this:

If you're wondering about the image processing algorithm behind this image, it's called primitive,
by Michael Fogleman, and you can also buy a Mac app that he built here.
We'll take this algorithm and build a mobile app around it, where the algorithm
is originally written in Go.

And this is how it will looks like live (video is on fast-speed):

Since the core algorithm is native, and built in Go, we'll use it as-is. But to
be clear, if we had to build this algorithm or any other algorithm or piece of
infrastructure that must be native, and we didn't have an existing codebase already,
we had a hard decision to make:
- Build it twice in Java and Obj-C.
- Once in C/C++.
- Once in Go.
Actually, for this particular use case and algorithm, we tick even more items from the "why go native" list above:
Performance. Go will exhibit better performance than Javascript (we'll
see by how much later). We need it here, since
primitive is a demanding cpu-based
algorithm. Actually, it's so demanding that it makes processing these
images on a strong iPhone slow. Go is closer to the metal
here and makes a good case to use it instead of Javascript.
Using a library that doesn't exist on npm. And this time, because it's a
novel algorithm implemented in Go, and there's no port of that any where
else, I was happy that Go on mobile (and later, I made it work on React
Native) works so well.
Obfuscation. In this case the target Go library we're using is open
source. If it weren't the case, and you cared about your IP, no one could
reverse engineer this algorithm without investing an unreasonable amount of
time.
Native API access. This algorithm can save progress snapshots to disk on
every iteration. If this were to be Javascript, we'd have to encode each
snapshot and send it over as a big base64 string across the React Native
bridge.
And of course, we can use the same native code for both Android and iOS.
The Go Mobile Toolchain
If you're already familiar with Go as a language, then this will be
a nice fresh breeze. If not, to get familiar with Go, check out the Go tour.
After you've done that, let's install and exercise the gomobile
toolchain (I assume
you've installed Go and were able to do a simple "hello world" with it in the meanwhile):
$ npm install -g ios-deploy
$ go get golang.org/x/mobile/cmd/gomobile
$ gomobile init # it might take a couple minutes
$ gomobile build -target=ios golang.org/x/mobile/example/basic
$ ios-deploy -b basic.app
If everything worked well, you should be seeing a mobile app in your simulator.
What this mobile toolchain did is build a native iOS library, generate a
skeleton app, link these, and package an app. It all happened quietly (or
humbly?), that's part of the Go philosophy: success should be silent; in other
words - no news is good news.
I encourage you to read the short Wiki article. To
get proper context about these tools.
Connecting React Native and Go Mobile
We start by making a fresh React Native app, and grab the react-native-camera
example here. This is
a convenient way to get to the point of this article but you may
want to build your own from scratch.
We want to build our native Go library now and start by connecting it to our
iOS app. This is where it might become tricky because we need to think about
our workflow before everything else.
In Go, the best thing to do is develop your Go code in your GOPATH
, which is
an opinionated place where your Go code and third-party libraries live
(there's a solid logic around that, but it's out of the scope of this article).
And so our first decision is to put our native library there as well - and not
alongside our app.
Once nice bonus is that our new Go package is ignorant of the fact that we'll
use it on a mobile device. In fact, you can conveniently work on it as a
stand-alone Go app and then decide to bind it to your mobile app when you're
ready.
For me, it lives here (yours will be different):
<home>/workspaces/golang/src/github.com/jondot/primer-bind
The layout is simple:
➜ primer-bind tree .
.
└── primer
└── prime.go
1 directory, 1 file
Let's take a look at our Go code:
A quick pass shows that we export OnIterationDone
which is a callback like
interface we'll use from each native side and Process
which is the entry
point to our algorithm. There's nothing special to do about types passing
in and out; go takes care of generating code for marshalling these to the
respective mobile platforms.
For Process
, I took the CLI code from primitive
and hacked it so it would
be a function I can export. We also export Bench
and JsonDummy
for our
benchmarking showcase.
You'll see that in each step in this article, I take the smallest possible
step to strategically create code that we can discuss without too much context.
Once this is in place, we have to switch context and cd
to our app. We
now want to make use of the gomobile
toolchain, so we'll run this:
gomobile bind -x -v -target=ios github.com/jondot/primer-bind/primer
It will generate a native library for you and spit a lot of detail while doing
so because we're using -x -v
; but you can drop these if you like. You can see
the beauty of treating every Go code like a package: we supply a path that is
both a repo and a local path in your GOPATH
, if you didn't have a local copy,
Go would fetch it and build using it, or leave this as-is and it'll grab my
package and compile it so that you wouldn't have to.
Next up, we'll have to build a bridge in our native code to
glue everything together.
Here, we're making an RCTEventEmitter
because we want to
get notified where the native Go algorithm finishes its work. We
also wrap our OnIterationDone
struct with a nicer Obj-C interface.
For where headers sit, it correlates with our Go
package (we chose primer
).
Observation: this is how you would build a native module regardless of Go. We want to have
an async function on the native side that gets variables such as iteration
count, size, mode and number of workers and a callback, pass these to
our Process
function, here called GoPrimerProcess
which is automatically
generated for us, again it correlates with package name and function name,
prefixed by "Go".
That's basically it. The Javascript part now kicks in, and again
we'll do this like any other native module glue:
Android
For Android, this will be even easier because we have Android Studio support
through a Gradle plugin. Remember, we still have our same Go package
implemenation, untouched, in the same place. We need to specify that location in Gradle
and wire the Android native bridge in Java.
These would be the same steps to do in any Android and Go app. For more information
you can look at the reference project and here.
Like every Android native component in React Native, we have our module which
contains the interesting bits and the package which instructs React Native to
inject our module in.
Now, our bridge code. The module part:
And the package part:
Our Java module code shows that the Go package magically appears in go.primer
and under a pseudo-object called Primer
(how convenient!). Note that gomobile
generates the Go side for you and is idiomatically lays it like a real Java
module.
This completes a grand-tour around: Go, iOS (bridge), Javascript (glue), and Android (bridge).
A Grand Tour
This has been a tour around four languages, doing plumbing work: In Go, we
wired the real Go module to make a function which we can export cleanly. On
iOS, we built a native module and bridged it to the native library, which isn't
different than any other C library for that matter. In Javascript, we wired the
native part to application-side code which isn't unlike anything we'd do for a
regular native module. Closing up, on Android, we set up a nice build
configuration and a simple native module bridge.
Most of this investment in plumbing work would be the same if we were to build
any native module. The difference here is that now we have a single native, Go
based, codebase to work with - and for the long-run this is going to save a ton
of time because we don't need to maintain the same parallel codebases on
Android and iOS.
The Performance Question
How does our code perform in Javascript compared to Go?
We'll run a silly benchmark, which probably is misleading for any real-life
decision making so don't take it as a fact.
First, let's define our benchmark. I've picked something that all languages should do, and that we do all the time,
which is JSON deserializing and serializing, this also matches one of the criteria in the TechEmpower
benchmarks for web frameworks. I don't know if that's the best micro-benchmark criteria, but
I feel it's a safe choice.
Let's say dump and parse a JSON object a bunch of times? Let's do it in Javascript,
and in Go, where each language will use its own facilities for that - no
passing of control between Go and Javascript, and it isolates each benchmark.
Go:
func Bench() {
for i := 0; i < 10000000; i++ {
_, _ = json.Marshal(JsonDummy{hello: "world", meaning: 42})
}
fmt.Print("Done\n")
}
Javascript:
for(let i=0;i<10000000;i++){
JSON.stringify({hello: 'world', meaning: 42})
}
In this benchmark Go performs 2x faster than Javascript on both platforms.
Conclusion
Go with React Native is a match made in heaven for the following scenarios:
- You are passionate about Go or already have existing code in Go, or want to share backend Go code with your
mobile app. Granted, specifically for that, you also have GopherJS which works exceptionally well.
- You need the performance margin.
- You want the infrastructural robustness and type safety values of Go.
- You want proprietary code to stay safe.
- You don't want to express ideas in both Obj-C/Swift and Java.
There are certainly more motivation to match Go with React Native but I
believe this small list was what you'll meet day to day. With that,
I hope you enjoyed this fusion of technologies and that you'll come to
build something interesting with the ideas I showed you here.
You can find the reference app here and the Go module here.
Also, I'm passionate about obliterating native code duplication. If you need
help doing this move, drop me a line at
dotan@paracode.com.
Read more →