Using Go for Mobile Apps: A retrospective on a year of development with Gomobile

newswangerd1 pts0 comments

Using Go for Mobile Apps

Home

About Me

Search

Dark Mode

Table of contents

Table of contents<br>I have been developing Digital Carrot with Go Mobile for the last year and it has been a largely positive experience. This article is meant to serve as a repository of everything I&rsquo;ve learned throughout the process.<br>Why did I choose Go Mobile?<br>The short answer is that I really like Go and wanted to use it. The longer answer is that is some combination of the following:<br>Digital Carrot is a cross platform application, and I wanted something that I could easily compile and run anywhere.<br>Go has a strong library of system features that I wanted to use to make Digital Carrot customizable and pluggable. Chief among these are Expr (for building expressions) and Goja (for creating JavaScript plugins).<br>How am I using Go Mobile?<br>Go mobile is used for all the business logic for Digital Carrot. While I would have loved to use Fyne for the UI, I gave it a try and decided it just isn&rsquo;t quite mature enough. Instead, I opted to use Flutter with a Go backend<br>Here is more or less what it looks like:<br>Flutter calls into Switf/Kotlin using platform channelsAPI calls are binary encoded protobuf messages<br>Native code forwards the raw binary to Go<br>Go decodes the protobuf messages and responds

Go can also call native code via interfaces when it needs to interact with native APIs<br>flowchart TD<br>native["Native Code (Swift, Kotlin)"]<br>ui["Flutter UI"]<br>go["Go Mobile Backend"]

ui -- Protobuf binary API calls --> native<br>native go<br>go -- Calls to native APIs (screen time, health) --> nativeFlutter to Go Communication<br>As mentioned above, Flutter communicates with Go using Protobuf messages. Protobuf is a MUST here as it allows me to work with nice structs/classes in Go and Dart without having to do a lot of manual marshalling and unmarshalling of JSON objects. I can define my messages in Protobuf and automatically get nice objects in Dart and Go to work with. Communication between Flutter, platform code and Go can only be done with basic data types (strings, binary, booleans, etc) so Protobuf messages are perfect for this use case.<br>Defining new platform channels is also a huge chore in Flutter since they need to be defined in three places (Flutter, platform and Go), so I ended up going with a single function that handles all API calls. This function passes a message with a large oneof block to define the actual API call. It looks something like this:<br>message CarrotAPI {<br>oneof api {<br>Function1API function1 = 10;<br>Function2API function2 = 11;

Each function call in the oneof block looks something like this:<br>message Function1API {<br>message Request {}<br>message Response {}

Request request = 1;<br>Response response = 2;

Flutter populates the request part of the message, throws it in the CarrotAPI object and sends it to Go. Go can tell what function is being called through a switch statement on CarrotAPI.api and populate the correct response. This is a little clunky, but it&rsquo;s much better than creating new platform channels for every function and saves a lot of work that would otherwise need to be duplicated across Swift and Kotlin.<br>I won&rsquo;t go into specifics on how to implement this. There are other articles that do a better job of explaining how this works.<br>Go to Swift/Kotlin Communication<br>This is one area that I struggled with for a long time because this is not documented well in Go. Essentially, to send messages from Go to platform code you need to create a Go interface that is then implemented on the platform side and passed into Go when the Go code is invoked. Here&rsquo;s an example:<br>We create a Go interface like this:<br>type IosMethods interface {<br>// Screentime<br>SetShields([]byte) bool<br>HasScreentimePermissions() bool

Go Mobile generates an objective C or Kotlin interface like this:<br>@interface MobileIosMethods : NSObject {<br>@property(strong, readonly) _Nonnull id _ref;<br>- (BOOL)hasScreentimePermissions;<br>- (BOOL)setShields:(NSData* _Nullable)p0;<br>@end

Which we can implement in Swift or Kotlin and pass back into Go:<br>class GoScreentime: NSObject, MobileIosMethodsProtocol {<br>public func setShields(_ p0: Data?) -> Bool {<br>return setShieldsFromJSONBytes(p0)

public func hasScreentimePermissions() -> Bool {<br>return AuthorizationCenter.shared.authorizationStatus == .approved

10<br>11<br>12<br>13<br>14<br>15<br>@main<br>@objc class AppDelegate: FlutterAppDelegate {<br>override func application(<br>_ application: UIApplication,<br>didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?<br>) -> Bool {<br>[...]

self.carrotApi = DigitalCarrot.MobileNewAppleMobileAPI(<br>GoScreentime()

[...]

Once again, these interfaces only support primitive data types. This is another area where Protobuf could be helpful, but I didn&rsquo;t end up using it since there aren&rsquo;t many calls that need to be made from Go to the platform code.<br>More information about this can be found in this excellent article.<br>How is all of this working out?<br>So far I really like this stack....

platform mobile flutter protobuf native rsquo

Related Articles