【マルチデバイスチーム ブログリレー2日目】
こんにちは、エンジニアリンググループ・マルチデバイスチームの小林です。 新卒3年目で、普段はAndroidアプリの開発をメインにしつつFlutterやiOSアプリの開発も行っています。
新年ということもあり、2022年の弊チームの主力アプリである「m3.comアプリ」の開発を振り返ってみようと思います。
大規模なリファクタリング
一番大きいのはこれでした。
以前テックブログで書いたとおり、2021年の秋頃から大規模なリファクタリングを行っていました。
m3.comアプリは「ニュース」「MR君」「Web講演会」「クイズ」といったm3.com内の様々なサービスにアクセスできる医療従事者専用のアプリとなっています。
現在は29サービス全てのリファクタリングが完了しています(設定画面やログ送信部分等、共通部分は一部残っています)。
またリファクタリングと並行して新規サービスの追加や機能修正も行うことができました。
ざっくりBefore & After
- Kotlin 20%、Java 80% -> Kotlin 85%、Java 15%
- シングルモジュール -> マルチモジュール
- Android Annotations -> Dagger Hilt
- EventBus -> 削除
- RxJava -> Kotlin Coroutines
- ActivityやFragmentにビジネスロジックが書かれている -> Flux + AAC ViewModel
詳細は記事をご覧ください。
Jetpack Compose
リファクタリングを始めた頃には既にJetpack Compose 1.0がリリースされていましたが、現在のUIと同じものを実現できるのか・安心して使えるかどうかの検証がまだ完了していなかったため、導入を見送っていました。
とはいえJetpack Composeへの移行は意識しており、
- data classで画面の状態を単一のクラスにまとめる
- ViewModelがStateFlowでその状態を公開し、Activity/Fragmentでそれを購読してViewを変更する
といった様に宣言的にUIを構築し、Jetpack Composeへの切り替えを行い易くしていました。
data class HogeUiState(...) @HiltViewModel class HogeViewModel @Inject constructor(...): ViewModel() { private val _uiState = MutableStateFlow(HogeUiState()) val uiState: StateFlow<HogeUiState> = _uiState.asStateFlow() ... } @AndroidEntryPoint class HogeActivity: AppCompatActivity() { private val viewModel: HogeViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { ... lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collect { ... } } } } }
そして安定版リリースから時間が経ち様々な企業での実績が増えてきたことを踏まえ、途中からJetpack Composeの導入を検討しました。
検証
まずは機能がシンプルなFragmentを1つ選び、検証してみました。
この画面は、
- 2種類のアイテムをリスト表示する
- リストのアイテムにサムネイルを表示する
- アイテムをタップするとWebViewへ遷移する
- Pull to Refreshで再読み込みをする
- アイテムが画面に表示されたときにログを送信する
といったもので、ここで実績を作ると他の画面に応用しやすいため選びました。
問題なく実現できたため、本格的にJetpack Composeを採用していくことになりました。
段階的導入
Jetpack Composeは従来のViewとの相互運用APIを使うことで簡単に部分導入できます。
ゼロからJetpack Composeを使ってアプリを開発するなら単一ActivityにしてNavigation Composeで画面遷移を実現するのが良さそうですが、 m3.comアプリは複数のActivityで構成されていたため、その状態に持っていくのは大変そうでした。
そのため、まずはActivityやFragment単位でJetpack Composeに置き換え、ナビゲーション部分は従来のままにすることにしました。
@Composable fun HogeScreen(...) { ... } // Activityで使う場合 class HogeActivity: ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { M3ComAppTheme { HogeScreen(...) } } } companion object { fun createIntent(...): Intent { ... } } } // 画面遷移は従来のまま val intent = HogeActivity.createIntent(...) context.startActivity(intent) // Fragmentで使う場合 class HogeFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { return ComposeView(requireContext()).apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { M3ComAppTheme { HogeScreen(...) } } } } }
結果、現在29サービスのうち21サービスでJetpack Composeが使われています。
Renovateを導入
ライブラリのアップデートを行うのって大変ですよね。
ライブラリが更新されたことに気づかない、いつのまにか更新が溜まっていた、大量のマイグレーションが必要...
放置すると後がどんどん大変になってしまうので、なるべく早く更新に気づき、こまめに更新できるようにRenovateを導入しました。
マージリクエストが作成される -> 更新内容をみて問題なさそうならマージ、対応が必要ならやる
という習慣が付き、溜まってからまとめて対応するということが無くなりました。
せっかくリファクタリングを行ったのに、ライブラリの更新をサボっていたらまたアプリの負債が溜まる原因になってしまうので、導入してとても良かったと思います。
Renovate導入にあたり、ライブラリの管理をVersion Catalogで行うようにしました。
また別チームのエンジニアがRenovate用のGitLab CIテンプレートを用意してくれていたため、導入もサクッとできました!
導入してまだ2ヶ月ですが、48個のマージリクエストをマージしました!
まとめと2023年にやりたいこと
2022年はm3.comアプリが一気にモダン化した年になりました。開発効率は格段に向上し、楽しく開発できています!
iOS版も同様にリファクタリングが行われ、開発効率が改善されています。
そして2023年にやりたいことはいくつかあります。
まだ少し残っている部分のリファクタリング
設定画面やログ送信部分等、共通部分の一部がまだリファクタリングできていないため、引き続きやっていきます。 もちろんKotlin 100%も目指します!
Jetpack Composeで書いている部分のスタイルを統一
Jetpack Compose化を進めている最中でも、もっと良い書き方を思いついたらそれを採用していくという感じで進めていたため、始めの頃に書いたコードと最近書いたコードを比べると実装スタイルがちょっと変わっています。
今は公式のアーキテクチャガイドがしっかり整備されているため、こちらになるべく寄せていきたいと思っています。
Kotlin Multiplatform Mobileの導入
去年Beta版になりましたね 🎉
弊チームのアプリエンジニアは全員Android・iOS両方の開発ができるため、KMMによるコードの共通化と相性がとても良いと思っています。
KMMの導入によってさらなる開発効率の向上を目指すべく、検証と導入を進めていきたいと考えています。
We are hiring!
マルチデバイスチームでは、OSを問わずモバイルアプリ開発が好きなエンジニアを絶賛募集しています。 興味がありましたらぜひご応募ください!