RxJava2へ段階的に移行しよう
RxJava2リリースされましたね、何ヶ月か前に。
OrmaやRetrofitがRxJava2に対応済みなので、個人的にもPairs的にもいつでも移行できる状態ではあります。
といっても大変ですよね。一体いくつのストリームがあると思ってるんですか!!
あれを全部RxJava2へ書き換えていくのはちょっと面倒ですね。できれば徐々に移行を進めていきたいところです。
RxJava1とRxJava2の相互運用
github.com
そこで便利なのが、RxJavaやIxJavaの開発を行っているakarnokdさんが開発しているRxJava2Interopです。
こんな感じで相互に変換を行うことが可能です。
io.reactivex.Observable o2 = RxJavaInterop.toV2Observabe(observable); rx.Observable o1 = RxJavaInterop.toV1Observable(observable, backpressureStrategy);
レイヤーごとにRxJava2に書き換えていく。
RxJava2Interopを利用すれば相互運用ができることがわかりました。 私の個人アプリやPairsではLayered Architectureを採用していますので、下の層の方(DaoやClient)から徐々にRxJava2に書き換えていけば良さそうです。
Kotlinでエレガントに
個人アプリもPairsもKotlinで開発しています。となると、
RxJavaInterop.toV2Observable(observable)
と書くのはちょっと面倒くさいな..となってきます。そこは拡張関数を利用して流れるように書きたいわけです。
observable.toV2Observable()
github.com というわけで、拡張関数を書いてまとめておきました。本家のメソッド名そのままに拡張関数を書いてるので、使い方に関しては本家をみてください。 面倒くさいのでまだJitPackつかってますがそのうちちゃんとあげます。 あとまだテストも書いてないです、そのうち書きます、またはPR待っています。
Java/Kotlin混合プロジェクトで気をつけていること。
弊社Slackの#eureka-kotlinからの転載シリーズ。 プライベートではすべてKotlinでコードを書いているのであまり気にすることがなかったのですが、pairsはJava/Kotlin混合で成り立っているのでKotlinで書いたコードがJavaからどう見えるのかを気にすることがたまにあります。 すべてを網羅しているわけではありませんが、いくつか書いてみました。
Companion object != Static
Kotlinにはstaticメソッドやstaticフィールドはありませんが、これらと同等の役割を持ったcompanion object
が存在します。
fun KotlinFragment: Fragment() { companion object { fun newInstance(...): KotlinFragment { ... } } }
しかしこれをJavaから利用するには以下のようにする必要があります。
KotlinFragment.Companion.newInstance(...);
これはいまいちですね。Javaからの利用が想定される場合には@JvmStatic
アノテーションを利用します。
fun KotlinFragment: Fragment() { companion object { @JvmStatic fun newInstance(...): KotlinFragment { ... } } }
KotlinFragment.newInstance(...);
Property != Field
Kotlinで定義したPropertyはJavaからはアクセサを通して利用することになります。
class Kotlin { val bar = "bar" }
kotlin.getBar();
これをJavaからFieldとして扱うためには@JvmField
を利用します。
class Kotlin { @JvmField val bar = "bar" }
kotlin.bar;
高階関数
Javaでは関数は第一級オブジェクトではありません。 関数を引数に取るKotlinの関数をJava8未満から利用する場合には、匿名クラスを渡す必要があります。
class Kotlin { fun higherOrder(func: ()->Unit) { ... } }
kotlin.higerOrder(new Function0<Unit>() { @Override public Unit invoke() { return null; } });
関数を返すKotlinの関数であれば、FunctionNが返ってきます。
class Kotlin { fun higherOreder(): ()->Unit { ... } }
Function0<Unit> func = kotlin.higerOrder();
予約語
KotlinでJavaの予約後を使ってしまうと、Javaから呼び出すことができません。
kotlin.final()
JavaでKotlinの予約後を使った場合、Kotlinから呼び出すことはできますが扱いにくいです。
java.`is`()
open
これはJavaからの利用に限った話ではありませんが、Kotlinのクラス、関数はopen
キーワードをつけなければすべてJavaでいうfinal
です。
テストを書く際にmockitoを使ってKotlinクラスをモックしようとしても、継承/オーバーライドすることができません。
mockitoを利用する場合にはモック対象にopen
キーワードをつけて継承/オーバーライド可能にしておきましょう。
Kotlin 1.0.6以降であればAll Open
アノテーションを定義することで一括ですべてをopenにすることも可能です。
KotlinのInterfaceはPropertyも定義できるんだよ
そういえば最近は仕事でもKotlinを書いています。同僚をKotlinで洗脳した甲斐があるというものです。 developers.eure.jp
しかし、全員が全員Javaを書く際と同じようにすらすらKotlinを書くことができるわけではないので、私がKotlinのTipsや文法などをつぶやくSlackのチャンネルがあり、みんなにはそれ読んでもらっています。 せっかくなのでこれからは自分のブログにも転載していこうと思います。 多少雑です。
tl;dr
- KotlinのInterfaceはデフォルト実装を定義できる
- KotlinのInterfaceは(バッキングフィールドを持たないものであれば)プロパティを定義できる
そういえば、(1.1からはできるが)data classは継承をサポートしてないので、デフォルト実装やpropertyをもつinterfaceを実装することで、なにかしら有効活用できそうですね。
KotlinのInterface
デフォルト実装を定義できる。
KotlinのInterfaceはJava8のInterfaceと同様に、デフォルト実装を定義できます。
interface Foo { fun doSomething() = "default" } class FooImpl : Foo { } // ------------------- val foo = FooImpl() foo.doSomething() // return "default"
Propertyを定義できる。
JavaではInterfaceにフィールドを定義することはできませんでした。
しかし、KotlinではBacking fieldを持たないPropertyであれば定義する事ができます。
interface Foo { val one: Int } class FooImpl : Foo { override val one = 2 }
Backing Fieldとは
うまく説明できる気がしないのでKotlinコードと、そこから生成されるByteCodeをJavaにデコンパイルしたものの対比をみて察してください。
Javaコード側でfieldの定義されているA, C, Eをbacking fieldを持ったpropertyといいます。
13日目:プロパティとフィールド - Kotlin Advent Calendar 2012 (全部俺)
case A
class Kotlin { val a = 2 } public final class Java { private final int a = 2; public final int getA() { return this.a; } }
case B
class Kotlin { val b: Int get() = 2 } public final class Java { public final int getB() { return 2; } }
case C
class Kotlin { var c = 2 } public final class Java { private int c = 2; public final int getC() { return this.c; } public final void setC(int <set-?>) { this.c = <set-?>; } }
case D
class Kotlin { var d: Int get() = 2 set(value) { } } public final class Java { public final int getD() { return 2; } public final void setD(int value) { } }
case E
class Kotlin { var e: Int = 2 set(value) { } } public final class Java { private int e = 2; public final int getE() { return this.e; } public final void setE(int value) { } }
AnkoでLayout XMLを殺した
そういえば先月頭にRettyさんでAnkoについてLTしたのでスライド置いておきますね。
speakerdeck.com
あ、ちなみに本稿は特にAnkoの解説は行ってないポエムです。
Ankoとは
Kotlin製DSL。Viewを書くのに利用します。
XMLでView書いた場合の以下のような問題を感じてJetBrainsのエンジニアが開発したのがAnko。
- 型安全じゃないし
- null安全じゃないし
- 似たようなことを毎回かかなきゃいけないし
- XMLをパースする分パフォーマンス悪いし
- コードの再利用できないし
こんな感じでViewを書けます
verticalLayout { val name = editText() button("Say Hello") { onClick { toast("Hello, ${name.text}!") } } }
Javaで書いた場合との差が冒頭に貼ったスライドに書いてあるので、興味ある人は見てください。
なにこれ魔法!?🎩
Kotlinの拡張関数とプロパティを利用してます。 拡張関数を引数にとる関数がどうたらこうたら。これも冒頭のスライドに書いてあるので興味のある人は見てください。
XML全部殺してフルAnkoにした
mints-Android-LayeredArchitecture 暇な時に開発してるアプリのViewをすべてAnkoで書いてみました。 (まだ改善できそうなところは多い)
(追記) HEADではもう、LayoutXMLとDatabindingを利用する形に書き換えてしまった。
ActivityやFragmentとは別のファイルにViewをまとめる
AnkoでViewを書いてる部分
class MainUi : AnkoComponent<MainActivity> { private lateinit var viewPager: ViewPager override fun createView(ui: AnkoContext<MainActivity>): View = with(ui) { verticalLayout { val tabLayout = tabLayout { tabMode = TabLayout.MODE_SCROLLABLE tabGravity = TabLayout.GRAVITY_FILL setSelectedTabIndicatorColor(getColor(ctx, R.color.colorPrimary)) }.lparams(width = matchParent, height = wrapContent) viewPager = viewPager { id = 1 offscreenPageLimit = 2 }.lparams(width = matchParent, height = matchParent) tabLayout.setupWithViewPager(viewPager) } } fun showPages(fm: FragmentManager, pages: List<PageAdapter.Page>) { viewPager.adapter = PageAdapter(fm, pages) } }
利用側
class MainActivity: AppCompatActivity() { private val ui = MainUi() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ui.setContentView(this) } }
XMLを書かなくていいのは結構いいですね。今回は一人での実装だったのでただの予想ですが、コードレビューでXMLを読むのが辛いのですが、Ankoなら多少軽減されるかも。 もっと細かい単位でAnkoComponentを実装していけばコードのreuseができそう。
MacBook Pro 13-inch with TouchBar
仕事用のMacBookPro 13inch TouchBar付きが届いたのでその感想。
tl;dr
- 薄い軽い、持ち運ぶ気になった
- ストロークの浅いキーボードなかなか打ちやすいぞ
- TouchIDが最高なことはみんなしってるだろ?
- TouchBarに最適化されたソフトウェア結構いいかも
- TouchBarカスタマイズして便利なショートカットつくれるぞ!
esc
は常に左上にあるから困らねーぞ!- functionキーは
fn
押せばでてくるぞ! - キーボードだとかモニタだとかじゃらじゃらつけないんでUSB Type-Cだけなのはこまらんぞ!
ボディ
前モデルに比べて軽く、薄くなった。
多少のさだが、結構体感できるほどに違う。これは正義。
今後は通勤時にも持ち運びしてもいいかなと思う。
キーボード
MacBookとおなじようなキーボード。
慣れてみるとこれがなかなかいい。
扱い始めて数十分後にはもとのキーボードのストロークの深さが気に食わなくなった。
TouchID
TouchIDが最高なことなんてiPhone/iPadつかってる人にはわかりきったことだよね。
TouchBar
よい。
BetterTouchToolsで独自のボタンをつくれるので、AndroidStudioでよく使うコマンドを割り当てたりした。
ソフトウェアがTouchBarに対応している場合もなかなかおもしろい。
たとえばSafariであれば、favorites画面でbookmark barに設置しているものがTouchBarに出ていたり
メディアの再生を(safariがフォアグラウンドになくても)TouchBarで操作できたり
これ地味に便利ね。safariでなにかメディアを再生してるときに話しかけられた場合、safariにきりかえて...タブひらいて...ってやらなきゃいけなかったのが面倒だったので(面倒だったのでそもそも止めないことも多かった)
Escapeない
気にならない。
物理ボタンではなくなったが左上には常に表示されている。
Functionキーない
以前よりワンストローク増えてしまうが、fn
を押せばTouchBarにFunctionキーが表示されるので困らない。
コマンド類は前述したようにTouchBarのカスタマイズで最適化できる。
TrackPad
カーソルスピード高く設定して使ってるので広くなっても特に恩恵はない。
誤タッチを気にしてるひともいるみたいだけど、ちゃんとパームリジェクション効くから落ち着けって。
とはいうものの、右下クリックをSecondaryClickに割り当ててると困る。
右下に手のひらが置かれてる状態でクリックするとSecondaryClickとして検出される。
諦めて2FingerClickでSecondaryClickにした。は〜。
USB Type-Cしかない
困らない。
外部キーボード??外部モニタ??キーボードもモニタもMBPにすでについてますよ!!
検証端末の接続もこまらない。Nexus5XはもともとUSB Type-Cです。
iPhoneはケーブルかわないとですね。は〜だるい。
電源
MagSafeはその名の通り安全で最高だった。
ただUSB Type-Cにもいいところはある。
- 右からでも左からでも給電できる
- モバイルバッテリーから給電できる
モバイルバッテリーから給電できるの最高でしょう?
総評
満足
Kotlin拡張関数は怖くない、その実態を紐解く。
Kotlin未経験Javaエンジニアに拡張関数を説明すると「怖い」と言われることがあります。 おそらく、クラスを継承する事なく拡張できることが黒魔術的に見えるがゆえの感想なのではないでしょうか。 本稿では拡張関数の実態を知ることで、拡張関数をもっと身近に感じてもらいたいと思います。
拡張関数とはなにか
拡張関数はクラスを継承せずに機能を追加するための機能です。
下記のようにfun Type.functionName(...)
とすることでそのType
に新たに関数を追加する事ができます。
fun String.println() { println(this) }
拡張関数は通常の関数と同じように呼び出すことが可能です。
"Hello World".println() // out: Hello World
拡張関数はどのように実現されているのか
StringExtension.kt
ファイルに実装した下記コードをコンパイルすると、StringExtensionKt.class
が生成されます。
fun String.println() { println(this) }
ではStringExtensionKt.class
をJavaへデコンパイルしてみましょう。
public final class StringExtensionKt { public static final void println(@NotNull String $receiver) { Intrinsics.checkParameterIsNotNull($receiver, "$receiver"); System.out.println($receiver); } }
ファイル名Kt
クラスに拡張関数と同名のstaticメソッドが生成されていることから、拡張関数がただのstaticメソッドであることがわかると思います。
ここまで分かってしまえば何も怖いものはありませんね。
private拡張関数
以下のようなFooクラスがあるとします。
class Foo {
}
これに対してFooExtensionファイルにprivate拡張関数を実装しましょう。
private fun Foo.doSomething() { } class Foo { init { doSomething() } }
さて、ここで一つ疑問が出てきました。先程の例と同様であれば以下のようにFooExtensionKtクラスにprivate staticメソッドが生成されるはずです。 となるとFooExtensionのprivate staticメソッドをFooから呼び出せる事はおかしいですね。
public final class FooExtensionKt { private static final void doSomething(@NotNull Foo $receiver) { } }
実際にコンパイルしてみると以下のようになりました。
public final class FooExtensionKt { private static final void doSomething(@NotNull Foo $receiver) { } public static final void access$doSomething(@NotNull Foo $receiver) { doSomething($receiver); } }
doSomethingへアクセスするためのブリッジメソッドが生成されています。 ここでFoo.classもデコンパイルしてみましょう。
public final class Foo { public Foo() { FooExtensionKt.access$doSomething(this); } }
なんと、private拡張関数を呼び出していた部分はブリッジメソッドの呼び出しに変わっています。 どうやらコンパイル時にブリッジメソッドが生成され、Kotlinからのprivate拡張関数呼び出しはこのブリッジメソッド呼び出しに置き換えられるようです。
では、もしFooがJavaクラスだった場合はどうでしょうか。 この場合そのprivateメソッドはまったくの役立たずになります。Javaからはただのstaticメソッドとしか見えない上にprivateであればアクセスする方法が無いからです。 コンパイル時に生成されるブリッジメソッドへのアクセスもコンパイル前では不可能です。
おわりに
どうでもいいですが、今回はバイトコードもそこそこ読みました。
普段全く読むことがないのでバイトコードを読む力はあまりないですが、これを機にちょくちょく見ていこうかなと思います。
Android StudioであればTools -> Kotlin -> Show Kotlin Bytecode
で即座に読むことが出来ますので、是非活用してみてください。
eurekaに入社して半年たった
eurekaに入ってから半年が経過。 pairsのAndroidアプリを開発してる。 どういう会社だとか書くのは面倒なのでやったことだけ軽くリストアップ。
やったこと
- Orma導入
- Kotlin導入
- Dagger導入
- Architecture改善
Orma導入
SQLiteいじるのにActiveAndroidを使っていた。ActiveAndroidは遅いし開発とまってるし、使い続けてもメリットは無いなということでOrmaへの移行を行った。
Ormaは速い。
クエリビルダはカラム単位でメソッドを自動生成してくれる。文字列を使ってカラム名を指定とかする必要がない。Typoによるバグは出ないし、テーブル定義でカラム名が変更されたらクエリビルダにも変更が加わるのでコンパイルが通らなくなる。
マイグレーションはリネーム以外は自動。
素晴らしいですね。
pairsはテーブル数多いしカラムも多かったのでそれなりに大変だった。
まったく根拠のない推測だけど、いまどきのネイティブアプリはDBそんなに複雑なものは無いと思うので大体の場合は大変ではないともう。
Kotlin導入
個人で一番書いている言語。仕事でも書きたいなと思ってた。
個人的にKotlinで気に入っている機能はnull安全と関数リテラル。
NPEが起こりうるコード書けない仕様になってる。
関数を変数に代入したり関数に渡したりできる。
チームに「Kotlinどう?」って聞くと、拒否反応は出なかった。
Kotlin勉強会を開いたり社内LT大会でKotlinを布教した。
フルKotlinにする予定は今のところない。ViewのみJavaで書いて、PresenterやModelはKotlinで書いてる。
追記: 結局Kotlin最高だなとなってそういった制限は撤廃された。
Dagger導入
後述するArchitectureの改善するにあたってDaggerがないと厳しい感じだったので導入。
Architecture改善
API周り・DB周りのインターフェースがstaticメソッドで、テストが書きにくかった。
class FooDao { public static List<Foo> findAll() { return App.getOrma().selectFromFoo().toList(); } public static void save(Foo foo) { App.getOrma.insertIntoFoo(foo); } }
class FooClient { public static Observable<Foo> fetch() { return Client.getRetrofit() .create(FooService.class) .fetch() .doOnNext(new Action1() { public void call(Foo foo) { FooDao.save(foo); } }); } }
リクエストの度にRetrofit#createをコールするので処理に無駄が多かった。
DB周りはOrma移行のときに改善するチャンスがあったが、移行と改善を並行させたら絶対コケると思ったのでインターフェースは全く変えてなかった。
依存する物(Service/Orma)はすべてコンストラクタで受け取るように変更。
ClientはRetrofitが生成したServiceを叩くだけのクラスに。
DaoはOrmaを叩くだけのクラスに。
RepositoryはClientで通信してDaoでINSERTする、Daoを叩いてSELECTする。
UseCaseは必要なRepositoryを使ってビジネスロジックをもつ。
PresenterはUseCaseとViewの架け橋的存在。
といった感じに。
他
最近は施策もやった。
あとはちょこちょこリファクタリングしたり、勉強会ひらいたり、AssociateEngineerやインターン生にレクチャーしたり。