「Flutter」と「Go」を組み合わせようと思ったきっかけ

永野峻輔氏:はじめまして。永野と言います。永野を英語で言うとエターナルフィールドなので、そんなTwitter名でずっと投稿していたんですが、この場で初めて音で聞くと、やっぱり恥ずかしいなっていう気持ちです。よろしくお願いします。

今回は、趣味で触っているFlutterと仕事で触っているGoを組み合わせてみるとおもしろそうじゃないかというところで、発表します。よろしくお願いします。

最初に自己紹介です。私はネットショップ作成サービス「BASE(ベイス)」を運営するBASE株式会社のグループ会社であるBASE BANKに所属しています。企画から開発など、全部をやっているロールです。ふだんはGoやPHPやPythonを書いています。

趣味でFlutterを書いていて、締め切りに追われるのも最近は日課という感じです。ちなみにこの資料も発表の10分くらい前まで修正していました。

Twitterなどでフォローしてくれるとうれしいです。

今日のトピックですが、なんでFlutterとgomobileを組み合わせたのかを話していきたいと思います。よろしくお願いします。

なんで組み合わせようと思ったかというと、まずFlutterはクロスプラットフォームのアプリケーションツールみたいになっていて、モバイルアプリケーションで作ろうと思うと、なんだかんだでAndroidとiOSのそれぞれでロジックを組むことがあります。

その差分の量が増えてしまうとFlutterの魅力が半減しちゃうので、それを極力減らしたいという気持ちがありました。Goと組み合わせられそうという話を見つけて、Goのエコシステムと組み合わせられたら最高なんじゃないかと思いました。

Google先生が作ったので、GoもFlutterも開発者体験がとてもいいので、これを組み合わせたら最強なんじゃないかというのが今回のモチベーションです。

これは大前提になるんですが、GitHubに行ったらわかるとおり、gomobileは実験機能みたいな感じなので、すぐ動かなくなります。みなさんも注意をしましょう。私も今回の資料を作るにあたってサンプルアプリケーションを作ったんですが、それなりに地雷を踏んだので、戒めとしてみなさんの心に掲げてください。

「Flutter」はプラットフォームで一貫したUIを提供できるのが魅力

そもそもFlutterって何なの? という話をしたいと思います。Flutterは先ほど言ったとおり、Google製のUIツールキット、フレームワークみたいなポジションになっています。

モバイルアプリケーションだけでなく、Webアプリケーションや、macOS用などデスクトップアプリケーションも対象です。今回はユースケースとして一番メインになっているモバイルアプリケーションに絞って話をしたいと思います。

Flutterの何がいいのかというと、Hot reload機能がやっぱりいいです。モバイルアプリケーションを作るときは、UIの試行錯誤をするシチュエーションが多いと思うんですよ。そのときにコンポーネントを1ピクセルずらして、再ビルドして1分、30秒と待つのはけっこうストレスだと思うんですが、FlutterだとHot reloadで、一瞬で終わります。

昨今のUIフレームワークは宣言的などの傾向があると思うんですが、先ほどから何回もくり返して言っているように、AndroidやiOSやWebなどに対応しているので、いろいろなプラットフォームで一貫したUIを提供できるのは、けっこう魅力だと思います。

プロダクションでFlutterを導入している企業も最近増えているかなと思います。

Hot reloadがどういうものかを動画でお見せしたいなと思います。こんな感じで縦並びに並んでいるコンポーネントを、Rowというかたちで書き直すと、一瞬で横並びになります。フィードバックループがすごく速くUIを作るので、とても強力なツールだと思います。

Flutterのアーキテクチャに関してもちょっと触れておくと、開発者は、altJSの1つであるDartを使って書きます。その下はC+などのネイティブのエンジンが動いているので、それなりのパフォーマンスをプラットフォームにも安定して提供してくれます。

FlutterがなぜDartを選んだかはいろいろと紹介記事があるんですが、その中で自分が思うのは大きくこの2つかなと思います。まず1つ目は、2種類のコンパイラが提供されていることです。

AOTと呼ばれる事前コンパイラが提供されているので、これによりリリースビルドがより最適化されるというメリットがあります。一方開発時は、Hot reloadなどで、Just in timeでコンパイルしてくれるので、リリースと開発のそれぞれでユースケースに対応しているのがすごく強力だと思います。

エコシステムとしてもそれなりに完成されていて、pub.devというレポジトリがあるので、これを探せばどんなライブラリがあるのかが一通りわかります。ほかにもいわゆるGoでいうplay ground的なdart.padなどがあって、Dartの文法も簡単に検証できるので便利です。

コードの量が肥大化すると「Flutter」の魅力は半減する

Flutter単体で完結ができないみたいな話をしたと思うんですが、結局ネイティブとの組み合わせが必要になってきます。内部的にはプラグインというかたちでネイティブの機能が必要で、ここに書いているようにMethod Channelがそれぞれのネイティブを裏側で呼び出しています。ネイティブはそれぞれSwiftやKotlinで書きます。

そこが分厚くなってくると、Flutterの恩恵があまりよくわからなくなってくるんですが、今回はgomobileを使って、ネイティブからGoのSDKを呼ぶという流れにしてみます。

「gomobile」は実験的に作られている機能

gomobileってそもそも何? なんですが、gomobileは今実験的に作られている機能で、実験から解放されてほしいという気持ちはあるんですが、実験的にと書かれています。とてもかわいいやつですね。

単体でネイティブアプリケーションが一応作れたり、ネイティブから呼び出すSDKなどが作れたりします。SDKの場合は、JavaやObjective-Cのコードが生成されて、それを呼び出して実現しています。

まず初期化なんですが、こういう感じでgo getしてgomobileを呼び出して、gomobile initすると動くようになります。今回の発表資料全体を通して、Goは1.15.8以上を推奨します。1.14系でも動くんですが、1.15の7未満だとcgoのバグが入っていて、動かない状態になるので気をつけましょう。

Androidを使う場合は、NDKが別途必要です。バージョンはこのあと実際にアプリケーションを作るときに説明します。

ところどころでエディタの画面を出しているんですが、FlutterやAndroid開発のときはAndroid studioを使っています。Swiftや、いわゆるiOS面の開発をするときはXcodeを使っています。

これ(gomobileは今実験的に作られている機能であること)を常に大事にしましょう。さっきのcgoのものも一緒に上がっているので、見てみるといいかもしれません。

ネイティブアプリケーションを簡単に作ってみました。go getでサンプルレポジトリを持ってきます。そのあとにgomobile buildのコマンドでAndroidとiOSのアプリケーションをそれぞれ作れます。が、iOSは結局動かせませんでした。どなたかわかる方がいれば、教えてください、というのが新しい方法です。

証明書周りでバグがあるようだったので、ちょっと静観するかという感じですね。どうやらハードコードされているバグがあるようで、今は修正中みたいですね。

Androidでは動いたので、動かしてみるとこういう感じになります。ちょっと目に優しくないですが、ネイティブで動いて「やったぜ」という感じですね。

次にSDKについて説明します。まず、Goのネイティブから呼び出すためのサンプルを書いてみます。実際それをネイティブで呼び出せるようにbindします。Androidの場合はNDKの設定が必要なので、その環境変数を通してあげましょう。これもgomobileのwikiに書いてあるので、見てみるといいかもしれません。

bindingされた中身を見てみると、JavaはGoと対応するような記述があって、Objective-Cでも同じように生成されています。Javaは一応読めるんですが、Objective-Cはちょっと読める自信がないので、あまり深くは掘りません。このあとにサンプルアプリケーションを書くので、それで使い方を見ていけばいいかなと思います。

Goで使った構造体や関数がどれだけエクスポートできるの? という話になってくるんですが、プリミティブな値は一通り使えるかなって感じです。ちなみにインターフェイスを使うと、Goからネイティブのコードを呼ぶ作りもできるので、goroutineを使ったコールバック処理を実装する場合は、そういうのを使うといいかもしれません。

返り値の2番目にエラーを書くことで、例外ハンドリングにも連携ができます。

なお、高機能であるSliceやMapが使えないので、ここは注意しましょう。

入力したテキストをパースして計算するアプリケーションを作ってみた

実際にどんなアプリケーションを作ってみたかというと、簡単なものでテキストを入力するとそれをパースして計算するアプリケーションを作ってみました。

サンプルリポジトリを載せているので、もしよかったら見てもらえるとうれしいです。わりと突貫で作ったので、バグもあるかもしれません。そこは甘めに見てもらえるとうれしいです。

なお、テキストを与えたときにパースするのには、govaluateのライブラリを使いました。この場を借りて、ありがとうございます。

開発の流れなんですが、まずFlutterのプロジェクトを作ります。そのあとFlutterとネイティブの連携をします。その次にgomobileと連携をして、gomobileとネイティブの連携をして、ゴールとします。Flutterに関しては、あまり深くは触れないので、そこはご了承ください。

Flutter doctorのコマンドで、自分のPCの環境がきちんと整っているかが見られるので、まずはこれをひととおり通せるようにしてください。

ここで出ている情報以外に、今回はNDKが必要なので装時点の22系の最新を入れています。実23系を試したらどうなるのかなとか思ったんですが、gomobileの挙動がいろいろと不安定なので、どこかのブログで見たものをそのままバージョンとして拝借しました。

ここのXcodeは12.4を使いました。Flutterだけではなく、gomobileの環境設定としてGo Projectをサブディレクトリに作っているので、一応そこも記載しています。

繰り返しになるんですが、Flutterとネイティブの関係はこちらです。メソッドチャンネルを通してサーバークライアント方式でやり取りをしています。いわゆるAPIとクライアントサイドみたいな関係になっています。

今回はこの連携にPigeonと呼ばれるコードジェネレーターを使いました。これはFlutterのCLIツールみたいなものですね。2.0系で発表されたもので、型安全にFlutterのコードとネイティブのコードが連携できるツールです。過去に「Qiita」で書いているので、詳しくはそれを見てもらえるといいかもしれません。

実際のコードの一部ですが、スキーマファイルを作ると、AndroidとiOSなどの通信をやり取りするようになります。

上のアプリケーションコードがいわゆるイベントハンドラーみたいなもので、計算するボタンが押されたときにこの関数が動くイメージでいてください。ネイティブのコードを呼び出して、そこから計算してくれるコードになっています。

「Flutter」と「Android」「iOS」の連携

先ほど説明したFlutterとAndroid側の連携について触れます。まずPigeonで作ったコードがいきなり怒られちゃうんですよ。これはなんでかというと、内部にインターフェイスのstaticメソッドを使っているからです。明示的にJVMのバージョンを1.8以上にする必要があるので、gradleの設定値を変えてあげます。

Android studioではgradleのsyncボタンみたいなものがあって、gradleを編集すると、いい感じに再設定してくれるのでやっておくと楽ですね。

実際に呼び出しコードを書いていきます。今回はApi.ktというかたちでKotlinで書いてみました。本丸のロジックはGoで書くんですが、いったんGo抜きで書くのでmock値みたいなかたちで、fakeという文字列を返すだけのものにしています。

Pigeonがいい感じにラップしてくれるので、こんな感じできれいに保管されて書けます。

書いたコードをFlutterのAPIでやり取りできるように、Api.setupをやっています。

次に、iOS側の設定も同じようにします。iOSの場合はプロジェクト設定にファイルを追加する必要があるので、FlutterにおけるiOS版アプリケーション名のRunnerをこのプロジェクトに紐づけます。

紐づけることでビルド時に一緒に生成物としてアーティファクトができます。実際に作られるものはObjective-Cなんですが、FlutterのiOS側のコードのメインは今はSwiftなので、SwiftからObjective-Cのコードを読むためのリンク設定をする必要があって、それをRunner-Bridging-Header.headerファイルに書く必要があります。

先ほどと同じようにmock値を書きます。同じようにsetup設定をエントリーポイントに追加します。

計算をしてみると、fakeというレスポンスが返ってくるのがわかるかと思います。

次にgomobile設定をします。Goのプロジェクトをあらかじめ作っておく前提です。文字列を与えると計算するコードを、こんな感じで書いておきます。1.15は先ほど触れたようにバグがあるので、8系以上にしましょう。

パッケージ設定を同じように書きます。Android側はaarというファイル、iOS側はframeworkというファイルを出力するようにします。

Android側の設定を書いて、先ほど出力したaarファイルをロードできるようにします。gradleを編集しているので、syncボタンを押すようにしましょう。このようなかたちで、先ほどfakeと書いたところに、Goで定義したメソッドを呼ぶようにしています。

2つ目をエラーで返すようにしていて、Kotlinからすると例外が飛ぶようになっているので、try-catchでハンドリングできるようにしています。

次はiOS側の設定です。iOSの場合は、Build Phasesからframeworksを設定することで同じような感覚で開発できます。Kotlinと違って参照を渡すかたちで、ちょっと書き方が違うので注意です。

これを書いて、FlutterからGoを呼び出すことが可能になりました。

参考までに、Flutterで開発したコードのパフォーマンスを見てみようと思います。こういう感じで、Android studioだとパフォーマンスが見られます。ただ、Flutter単体で動かしたものとgomobileと組み合わせたものであまり違いはなかったなというのが感想でした。

ファイルサイズはやっぱりgomobileを使うほうが増えました。

使えるところもあるがファイルのサイズには注意

まとめです。Flutterで差分を減らせたり、Goのライブラリを簡単に呼んたりできました。一方でファイルサイズが増えるので気をつけないと、と思いました。あと、使える型にも制限があるので、なんだかなという感じはなくはないです。

でもパースみたいな処理などをGoのコードを使ってシュッと書けるので、使えるところにはやっちゃっていいかなという気持ちです。

今回はモバイルアプリだけだったんですが、Webやデスクトップアプリやプラットフォームなどが増えてくると、より広範囲に対応しているWebAssemblyなどがおもしろそうです。

以上で発表を終わります。ありがとうございました。