Wantedlyのモバイルエンジニアの久保出です。今回は、Wantedly VisitアプリにおいてReact Nativeをやめる決断をしたこと、関連してKotlin Multiplatformを導入しようとしていることについて書かせていただきます。
なぜReact Nativeが導入されていたか
VisitのiOSアプリは2018年にフルリニューアルしました。リニューアルプロジェクトはモバイルエンジニアを総動員して半年近くかけてリリースしました。
リニューアルでは、色々なコンテンツを見つけられるDiscoverという新機能追加も予定しており、Discoverの実験もリニューアルと並列して行うことになりました。モバイルエンジニアはリニューアルに集中していたため、Webエンジニアのリソースが使えるReact NativeがDiscoverの実装手段に選ばれました。Discoverは、リニューアル前のアプリに組み込まれて検証が進められ、良い成果が得られたためリニューアル後にもそのまま組み込まれることとなりました。
後追いでAndroidにもDiscoverは実装され、アプリのアーキテクチャは次のようになっていました。
VisitにおけるReact Nativeの問題
あくまでもVisitにおいて起きた問題であり、React Native自体の問題点ではありません。
メンテナー不在
- Discoverの開発が落ち着くとWebエンジニアは本体のWeb開発に戻ったが、それ以降React Nativeの開発は止まりメンテナーが不在になった。
- Xcodeやビルドツールのアップデートをする際にReact Native自体のアップデートをしないと進められず、React Nativeのアップデートも当時は大変だったため必要以上のコストがかかっていた。これによりメンテが苦痛みたいな雰囲気が出てしまった。
技術スタックの違い
- モバイルエンジニアがメンテしようにも、React Nativeについてのスキルを持っておらず、学習コストが必要だった。
- 後追いでAndroidにも導入されたことによって、両OSの知識をある程度持たないとReact Nativeを触りづらい状況になった。
UI/UXの不一致
- iOSでしか動かない実装が一部に存在したため、Androidに後追い実装される際に機能を削ることになり、両OSで体験が揃わなくなってしまった。
- ネイティブの画面とReact Nativeの画面とが共存するため、同じアプリなのに微妙にUIが異なる箇所があった。
生産性への影響
- フルビルドが遅くなる、ツールチェインが増えることによる問題など
そして取り除くことに
長い間これらの問題は解決されないままになっていましたが、ちょうど自分がReact Nativeに手を入れる機会があったため、その上で今後の方針を決めることにしました。Hot Reloadの存在やUIの実装のしやすさなど生産性の高さは実感できたのですが、現状問題のほうが大きいと考えて、まずはAndroidから取り除く決断に至りました。
Kotlin Multiplatform
モバイル全体の生産性を考えたときに、両OSで実装しなければならないのはコストが高く感じており、APIのスキーマ実装が微妙に違うといった差異を減らしたかったのもあり、このタイミングでクロスプラットフォーム技術の一つであるKotlin Multiplatformの導入も検討することにしました。
Kotlin Multiplatform(Kotlin MPP)とは、Kotlinで書かれた単一のコードを複数のプラットフォームで動作させる仕組みです。
引用: https://kotlinlang.org/docs/reference/multiplatform.html
- Common Kotlinのコードを書くことで、各プラットフォームで動作するトランスパイルされたコードが生成される。
- 各プラットフォーム向けの固有のコードもKotlinで書くことができる。
- expect/actualという仕組みがあって、プラットフォームごとの違いを吸収できる。
Kotlin Multiplatform Mobile(KMM)というモバイルアプリに特化したユースケースを紹介するサイトも公開されており、その中ではビジネスロジックはMPPで共通化し、UIでは各プラットフォームで実装する手法が紹介されています。
今回は、KMMのユースケースに沿う形でAndroidから導入を進めることにしました。
MPPの設計
Visit iOSではReactorKitというFluxのフレームワークが採用されていて、AndroidはReactorKitと使用感が同じになるようにインターフェースを寄せた独自のViewModelを採用していました。
MPPはUI以外のレイヤーを共通化することに決め、更にReactorKitに使用感を似せたアーキテクチャを採用することで、アーキテクチャ面での学習コストを極力下げるようにしました。
ライブラリは
- Kotlinx Coroutines
- Ktor Client
- Kotlinx Serialization
- Single source of truthのためにSQLDelight
などを採用しています。ほとんどはMPPのショーケースであるKaMPKitと同じような構成になっています。
現在のVisitアプリ
執筆時点では、AndroidからはReact Nativeがもう少しで取り除けるような状態になっています。
もともとWebViewで表示していたStory画面をMPP+AndroidネイティブUIで実装し直したことにより、PVが40%改善するなど嬉しい結果も得られました。
将来的にはiOSにも導入し適用範囲を広げていき、モバイルプラットフォーム全体の生産性改善につながることを期待しています。
現状のMPPのPros/Cons
Androidでの導入途中で自分が感じたMPPのPros/Consを書いておきます。
Pros
- Androidからは、一つのPure Kotlinモジュールとして扱えるので、Androidプロジェクトへの導入は簡単。とりあえずAndroidのビジネスロジックはMPPで作っておいても損はなさそう。
- 仕様さえ決めれば、AndroidでのUI実装とMPPのロジック実装を二人で並列して進められる。
- MPPにはUIがないため、必然的にテストを書くことになり、テストを書く文化と高いカバレッジが得られる。
Cons
- まだAlphaで、動作はするがリスクは背負ってねというステータス。
- iOS(Kotlin/Native)におけるメモリ管理の難しさ。Kotlin/NativeにはKotlin/JVMにはないImmutabilityの概念があり、Coroutinesを使っているとInvalidMutabilityExceptionを実行時によく目にすることになる。
- 改善することは予告されているが、現状はプログラマが実行スレッドに気を使わなければならない。
- iOSから見たらRNと変わらないのでは?という疑問。
- そのとおり。RNとは違って、プラットフォームの知識が一番必要なUIは共通にしないため、RNほどの乖離は発生しないだろうと期待はしている。チームへ導入するためのガイドも存在するので参考にしつつ、皆がメンテできる体制を整えるのが課題になってくる。
まとめ
Wantedly VisitアプリにおけるReact Nativeを導入、やめるに至った経緯、そしてKotlin Multiplatformの一つの事例を紹介させていただきました。取り除くのはまだ道半ばですが、React Nativeの反省を活かしつつ、MPPのような新たな試みを今後も進めていくつもりです。