KotlinでFlutterアプリを書きたい

@kikuchy

Who?

  • @kikuchy
  • 菊池 紘
  • 株式会社Diverse(ミクシィグループ→IBJ)
  • KotlinはM13のときから使ってました
  • Dart歴3週間くらい
  • 会社でPodcast配信してます。聴いてください!
    https://podcast.diverse-inc.com/

Flutter
流行ってますね

開発言語
Dartですよね

このご時世にNull-Safetyじゃないとか厳しすぎる
※個人の感想です

このご時世に行末セミコロンとか書きたくない
※個人の感想です

KotlinでFlutter開発できたらこんなメリットが

  • Kotlinの強力な言語機能を使用できる
    • Null-Safety
    • Smart Cast
    • Class Delegation / Property Delegation
    • 簡潔な文法
    • などなど…
  • Android開発者がすぐに馴染むことができる
    • Google I/O 2017以降、Kotlinを使うAndroid開発者が激増した

頼む〜Flutter対応してくれ〜〜

2018年6月27日のKotlin Developers Meetupにて

JetBrainsのHadi Hariri氏に質問できる機会

「KotlinでFlutter開発できるようなる予定はありますか?みんな待ち望んでますよ!」


「えー、本当に?(疑いの目)
じゃあこの会場でKotlinでFlutter書きたいって人
手挙げて」

(ちらほらとしか挙がらない手)

「書きたいって言ってるのお前の友達だけじゃん!」

本当かな…?

KotlinでFlutterアプリ

作りたい人挙手!

たくさんいますね!!

ということして話を進めます

その願い、叶えましょう!

……

駄目でした

今のところは

個人的な原因

  • Flutterのエンジン周りの仕組みを理解しきれなかった
  • Dartの処理系を理解しきれなかった
  • Kotlinのコンパイラ周りを理解しきれなかった
  • ↑すべて解決するだけの勉強を一人でするには時間が足りない

今 私にわかること全てをお伝えします

Flutterのエンジン周りに詳しくなっていただいて、
Kotlin on Flutterを目指そうと思っていただくのが目標です

1. Kotlin vs Dart

概要

Kotlin

Dart

発表・登場

2011年7月(約7年)

2011年10月(約7年)

主な開発者

JetBrains

Google

最新バージョン
(2018/08/31時点)

1.2.70

2.1.0

主な用途

Android開発

サーバーサイド

Webフロント

Flutter

Webフロント

特徴的な言語機能(型システム/変数)

Kotlin

Dart

型推論

null安全性

再代入禁止

val

final

代数的データ型

(sealed classを使えば可能)

型変数情報を
ランタイムでも参照可能

(Kotlin/JVMの場合)

特徴的な言語機能(クラス実装 1)

Kotlin

Dart

「継承より移譲」の実現機能

Class delegation

なし

カスタム getter/setter

(バッキングフィールド自動生成)

(バッキングフィールドは自分で用意する必要がある)

メンバの初期化略記法

(宣言も同時に可能)

値クラス宣言の略記法

data class

なし

特徴的な言語機能(クラス実装 2)

Kotlin

Dart

Mixin


(デフォルト実装付きinterfaceで可能)

コンストラクタのオーバーロード

(名前も付けられる)

子クラスのメソッドオーバーライドを明確に識別可能


(overrideキーワードが必須)

(@overrideアノテーションは任意)

特徴的な言語機能(関数関連)

Kotlin

Dart

任意引数を設けることができる

(デフォルト値指定で可能)

メソッド呼び出し時に
引数ラベルを付与できる

(任意のメソッドで可能)

(Named optional parameterとして宣言した仮引数のみ)

ラムダ式のサポート

async/await

関数が第一級市民である

特徴的な言語機能(その他全般 1)

Kotlin

Dart

ifやtry-catch、switch-case は

同値比較以外の場合にswitch-caseを使用

できる
(when文)

できない

例外として飛ばせるのは

Throwable

任意のインスタンス
(Error or Exception以外は非推奨)

特徴的な言語機能(その他全般 2)

Kotlin

Dart

ListやMapなどの
コンテナ型リテラルが

ない

ある

classとinterfaceの宣言

別個

同時
(classを宣言すると暗黙的にinterfaceも出来上がる)

レシーバ指定関数

作れる

作れない

特徴的な言語機能(その他全般 3)

Kotlin

Dart

戻り値がvoid(Unit)のメソッド呼び出し後にインスタンスを省略して次のメソッド呼び出しが可能

apply()スコープ関数

カスケード記法

文脈的に変数の型が明白な場合に自動的にキャストする

Smart Cast

なし

行末セミコロン

不要

必要

Dartの利点ももちろんある

  • Dartのように暗黙的interfaceがあった方がテストダブルへの差し替えが簡単
  • クラス内部からカスタムgetter/setterを介さずにバッキングフィールドを
    触れる
  • 「Javaを覚えたばかり」という人が第二言語として学ぶならDartの方が楽

Dartもいい言語

2.Dartを理想の言語に近づける

そもそもKotlinでなければ駄目なのか?

  • 別にDartが使いやすければ問題ないはず
  • (Kotlinは好きだけど)我々が真に求めているのは
    • 書いてて楽しくて
    • 簡潔に書けて
    • 生産性が高くて
    • 簡単に覚えられる言語
  • Dartが改善されればそれでよい

GithubのIssueを覗いてみよう

( ゚д゚) 7年前から議論が続いている…?
※Google Code時代

( ゚д゚)これも7年前から…

(; ゚д゚)激論が交わされ続けている

待てない

FlutterをKotlinで書こう

3. Flutterの構造と取りうる戦略

Flutterの概要

  • マルチプラットフォームなアプリケーションフレームワーク(iOS/Android)
  • プラットフォームのUIを使わずに、画面を自前で描画している
    • 画面出力にはOpenGLを使用
    • そのため、エンジンが対応できでシェルを用意できれば
      どのOSでもGUIアプリを提供できる
  • フレームワーク(ライブラリなど)はDart
  • エンジンはC++

他言語からのアプローチ戦略

Dartのフレームワークに頼らずFlutterのエンジンを直に使う

Dartの言語基盤に乗ってFlutterフレームワークを流用する

メリット

C++との接続が可能な言語なら
実装が可能

Flutterの資産を使用することができる

デメリット

各プラットフォーム向けに実装を用意する必要がある。

そのままでは既存のFlutterの資産は全て使えない。

Dartのランタイムと使いたい言語の相性が悪かったりするとそもそも移植できない?

Engine

C/C++

Embedder

Platform specific

Dartのフレームワークに頼らず
Flutterのエンジンを直に使う

Flutterエンジン直叩きの先駆者

  • XamarinのMVP、Adam Pedleyさんが6月にAndroid上で実現
  • Dartの起動シーケンスを除外してJNI経由でXamarinからFlutterエンジンのAPIをコール
  • FlutterのWidgetなどを使うためにはC#でライブラリの再実装が必要
    (なのでDart→C#トランスパイラを作ろうとしているが道程は遠い)

Engine

C/C++

Embedder

Platform specific

Dartの言語基盤に乗って
Flutterフレームワークを流用する

Framework

Dart

やった人はいなさそう

この方針で行きます

4.Dart資産を流用する戦略

Dart資産を流用する戦略

この方針でもいくつかの手段がある

  • Dartランタイムを他言語から操作する
  • 他言語からDart中間表現へコンパイルする
  • 他言語からDartへトランスパイルする

Dart資産を流用する戦略(比較)

Dartランタイムを
他言語から操作する

他言語からDart中間表現へ
コンパイルする

他言語からDartへ
トランスパイルする

メリット

Dart言語と他言語の文法要素の対応について考えなくて良い

FlutterのSDKをすべて使える。

Dart⇔他言語の相互呼び出しが可能になる可能性がある。

FlutterのSDKをすべて使える。

Dart⇔他言語の相互呼び出しが可能になる可能性がある。

モジュールの依存関係やFlutterの内部など難しいことを考えなくて良い。

デメリット

Hot Reloadは使用できない。
現状のFlutterでは使えない。

Dart中間表現について詳しくないといけない。

依存関係解決も自前で実装する必要がある。

コンパイル前にトランスパイルを挟むので時間がかかる。

互換性のない言語機能が出てきたときに問題になる。

4-1.Dart Native Extension

Dart Native Extension

  • いわゆるFFI
  • Dartからネイティブコードを呼び出す仕組み
  • Dartランタイムを操作するAPIもあるため、
    相互呼び出しを気にしないのであればネイティブコード側から
    Dartの全機能を使用することができる
  • https://www.dartlang.org/articles/dart-vm/native-extensions
  • Kotlin/NativeのC Interopを使ってDartのAPIを叩くことは成功

がしかし

Launching lib/main.dart on iPhone X in debug mode...

[VERBOSE-2:engine.cc(158)] Could not prepare to run the isolate.

[VERBOSE-2:engine.cc(117)] Engine not prepare and launch isolate.

[VERBOSE-2:FlutterViewController.mm(411)] Could not launch engine with configuration.

Syncing files to device iPhone X...

import 'dart-ext:sample_extension';

何種類かある内の
いずれかの種類のIsolationConfigurationを使用した

Isolationの起動に失敗する

|←Issue| ┗(^o^ )┓三

Flutter開発陣の方針とこの方法が使えない理由

  • Dart Native Extensionのユースケースは通常、既存のネイティブコードを
    使用するケース
  • それならばPlugin経由で各プラットフォームのネイティブコードを
    呼び出す仕組みを使って欲しい様子
  • DartランタイムのAPIはDart Native Extensionとしてロードされた
    共有ライブラリからでないと叩けない
  • (Issueは閉じられていて、Extensionをサポートするつもりは今の所
    なさそう?)

4-2.Dart中間表現にコンパイル

Dartの中間表現とは

  • Dart Kernel(拡張子は.dill)
  • バイナリ表現されたDartのASTや定数テーブルなどが入っている
  • 依存を解決した結果全部まとめたやつ
  • バイナリ表現のフォーマットなどはDart SDKのリポジトリ内に
    簡単なドキュメントがある
    https://github.com/dart-lang/sdk/blob/master/pkg/kernel/binary.md

import 'dart:core';

void main() => print("Hello World");

main = main::main;

library from "file:///Users/kikuchy/tmp/helloonly/lib/main.dart" as main {

import "dart:core";

static method main() → void

return core::print("Hello World");

}

constants {

}

Flutter内でのDart Kernelの使われ方

  • アプリ開発者が書いたコード(と依存コード)はコンパイルされ
    app.dill にまとめられる
    • デバッグ実行時にHot Reloadで転送されるアプリケーションコード
    • これが出来上がる流れはTakahiromさんの”Exploring Flutter in Android”を読むとわかる
      https://medium.com/@takahirom/exploring-flutter-in-android-533598ba17d2
  • これを使ってIsolate(Dartの仮想マシンにあたる)を起動する

apk/ipa

Flutterを動かす根本的な
ライブラリ群

参照/実行

コンパイル

※大体の動作イメージであり、正確ではありません

app.dill

転送

app.dill

apk/ipa

Flutterを動かす根本的な
ライブラリ群

参照/実行

コンパイル

※大体の動作イメージであり、正確ではありません

app.dill

転送

app.dill

ハードル

  • コンパイルと同時にimportによる依存関係の解決を行わなければならない
    • Dartの基本機能を利用するにはDartの標準ライブラリは解決できないといけない
    • Flutterの機能を使用するなら通常のライブラリの解決もできないといけない
    • KotlinコンパイラがDartの依存解決をどう行えば良いのか
  • KotlinコンパイラからDart Kernelのバイナリ表現を吐き出させる
    • Kotlinから直にDart Kernelを吐くのが難しいならJava Classファイルから?
    • 文法の対応を考えないといけない
      • coroutineとasync/await
      • クラスと暗黙的interfaceの同時生成
      • Threadモデルの違い
      • など

アイデア(1)

  • Android.jar方式のダミー
    • Kotlinコンパイル前にライブラリサーチパスにあるDartファイルを探索して、実装のないClassファイルを用意、コンパイル時にDartのコードをdillにして統合する
    • Kotlin→dillだけでなく、class→dillもできる可能性あり?

// lib/core/print.dart

void print(Object object) {

String line = "$object";

if (printToZone == null) {

printToConsole(line);

} else {

printToZone(line);

}

}

@DartSource("core/print.dart")

fun print(object_: Any): Unit {

throw UnsupportedOperationException("dummy")

}

予め標準ライブラリなどをダミーの

Kotlinコードに落としておく

import dart.core.*

fun main(args: Array<String>) {

print("Hello World")

}

main = main::main;

library from "file:///Users/kikuchy/tmp/helloonly/lib/main.dart" as main {

import "dart:core";

import "dart:async";

static method main(core::List<core::String> args) → void

return core::print("Hello World");

}

constants {

}

コンパイル時にアノテーションなどから

Dartのimportなどに置き換える

アイデア(2)

  • 独自のKotlinコンパイラ実装
    • JetBrainsがKotlinコンパイラの基本機能の開発を補助するライブラリを公開している
      `org.jetbrains.kotlin:kotlin-compiler`
    • 構文解析はこれに任せられそう
    • 明確なドキュメントはない
    • K2JVMなりK2JSなりK2Nativeなりを読んで使い方を理解しておく必要がある(できてない)

4-3.Dartにトランスパイル

通常のFlutter処理フロー

トランスパイルの手法

  • PSI(Program Structure Interface)TreeまたはAST(Abstruct Syntax Tree)を使う
  • ソースコードをトークン単位に分割し、意味のあるまとまりにしたもの
  • PSIは言語によらず共通のPSI Elementを使用できる
  • Kotlinの言語要素を解析し、要素が対応するようにDartの言語要素を構築し、Dartのソースコードに戻す

KotlinのPSI Tree/AST

DartのPSI Tree/AST

ハードル

  • ソースコードをパースしてPSIなどを吐くのに何を使ったら良いのか
  • 言語機能的/文法的に対応するものがない機能はどうしたらよいのか
    • DartKernerlにコンパイルするときと同じ課題

アイデア

  • J2Kを参考にする
    • “Convert Java File to Kotlin File” の中身
    • KotlinのPSI Tree解析結果をJavaのASTに組み直してJavaソースコードを書き出している様子
    • 読めてない

まとめ

  • KotlinにあってDartに無いものがある(逆も然り)
  • Kotlinにあるものを使ってFlutterアプリ開発をしたい
  • 多分DartはKotlinっぽくならない
  • Flutterらしいアプリ開発をするにはDart資産をKotlinから使える必要がある
  • Dart資産を使うには、FFIを叩く、KotlinからDartKernelもしくはDartを出力する必要がある

JetBrainsさん

KotlinのFlutter対応
お願いします…

みなさま

お知恵をお貸しください…

時間はかかるでしょうけれど

これからも頑張ります

KotlinでFlutterアプリを書きたい - Google Slides