every Tech Blog

株式会社エブリーのTech Blogです。

AWS Summit Japan 2024に参加しました

この記事は every Tech Blog Advent Calendar 2024(夏) 23日目の記事です。

こんにちは 開発本部データ&AIチームでデータエンジニアを担当している塚田です。

今回は先日行われたAWS Summit Japan 2024に現地参加する時間が取れましたので、 昨日の「Kotlin Fest 2024 に ひよこスポンサー として参加してきました!」に引き続きイベントレポートですが、個人的に印象に残ったことや聴講したセッションについて触れたいと思います。

tech.every.tv

セッション動画やセッション資料も期間限定で公開されているようなので、気になる方はそちらもご確認いただければと思います。

現地で参加したセッション(1日目のみ)

どのセッションも満席で熱量の高さを感じました。 また、セッション内で「生成AI」という単語を聞くことがないと思うほど頻繁に耳にしました。

基調講演 AWSと創る次の時代

1時間30分の大ボリュームだったので詳細は省きますが、

やはりAIについて触れられることが多く「責任あるAI」が何回も出てきており

その言葉からもあるようにAIが当たり前になり生活に溶け込んでいくようになっても

常に意識するべきことであることを再認識しました。

Amazon Q Businessの日本語対応が発表され、使うことができるのを楽しみにしています!

Dive deep on Amazon S3

S3をより深く理解するため3つの軸で詳しく説明がありました。

  • フロントエンドの理解
  • インデックスの理解
  • ストレージ層の理解

その他にもAmazon S3 Express One Zone誤削除に対してにも言及がありましが、ここでは3つの軸についてまとめたいと思います。

フロントエンドの理解

  • ピークのトラフィックは1PB/秒を超える規模となっている
  • 利用者側でもできる緩和策がある
    • マルチアップロード
    • 独自実装せずにCommon Runtime(CRT)を利用しベストプラクティスに則る
  • 新しいマウントポイントがいくつか出てきている
    • S3マウントは新しいマウオンとオプションが出ているけれど、いまだにバッドプラクティス
    • それなのに新しいマウントポイントが出てくる理由としては機械学習ニーズが大きい

インデックスの理解

  • 350兆のオブジェクトを管理しており、1億リクエスト/秒を処理している
  • 利用状況とキャパシティを常に監視し適切にスケールを行っている
  • プレフィックスごとにPUTとGETのリクエスト数の上限が設定されている
    • それを超えると503を返却する
  • キー配列によって負荷が変わる
    • キー名のカーディなりティは左に寄せる
    • キー名に日付を入れる際はできるだけ右側にする

ストレージ層の理解

  • 何百万ものハードドライブを利用して、エキサバイトのデータを保管している
  • データは常に冗長化された環境で保管され保存時、保存後定期的にチェックを行なっている

感想

AWS re:Invent 2023でも同じような内容があったと記憶していますが、日本語で聞くことができ理解がより深まったと感じました。

冗長的にオブジェクト管理をしていたのは理解していましたが、どういう体系で管理されて利用できているかの理解を深めることができました。

S3 Express One Zoneの利用場面がいまいちイメージでできていませんでしたが、機械学習などI/O性能が上がることでメリットを受けられるなど理解が進みました。

データ×生成 AI - 事例から学ぶビジネスインパクト創出の方程式

  • クラウドネイティブが進んでいる企業は生成AI活用も進んでいる傾向がある
  • 活用が進んでいる企業では共通するポイントがある
    • 顧客起点文化
    • 小規模チーム
    • 頻繁な実験
  • 小規模チームでも大規模かつ機微なデータでも迅速に生成AIプロダクトが構築できる
  • Amazon QやKnowledge bases for Amazon Bedrockを活用する
  • 責任あるAIを作るためにも責任共有モデルに基づいた実現が必要になってくる

感想

活用が進んでいる企業に共通するポイントはなるほどと感じる部分が大きく責任あるAIを作っていくためにも上記に記載したもの以外もしっかり考えてアクションしていきたいと思いました。

EXPO

セッション以外にも様々なブースがありましたので、少しだけ触れたいと思います。

Partner Solution Expo

たくさんのパートナーブースがあり、盛り上がりがすごかったと感じました。

また、利用しているサービスのブースもありましたのでオフラインでの質問や相談ができたのがよかったです。

ご対応いただいた皆様ありがとうございます。

認定者ラウンジ

認定者ラウンジを利用させていただきましたが、利用するのにも多数の参加者が並んでおり資格取得者の多さに驚きを覚えました。

保持している資格によってステッカーがいただけたのですが、おまけでクッキーもいただきました。

あとで知りましたが、AWS Certified Data Engineer - Associateを持っているとクッキーももらえるようになっていたようです。

まとめ

たくさんのインプット、知識の更新をすることができ、インプットしたことを業務やプロダクトに還元できるようにしていきたいと強く思える場になりました。

生成AIなど進歩が早い中で頻繁に実験を繰り返し開発する文化をより強くしていきたいと考えています。 1日目最初のセッションが始まる30分前には多数のサインがありました!盛り上がりがすごい!

Kotlin Fest 2024 に ひよこスポンサー として参加してきました!

はじめに

こんにちは、株式会社 エブリー DevEnableグループです。

先日のGo Conference 2024に引き続き、本日、約5年ぶりのオフライン開催となったKotlin Fest 2024にひよこスポンサーとして参加してきました!

Kotlin Fest運営の皆様および参加された皆様、お疲れ様でした!

早速参加レポートをさせていただきます。

www.kotlinfest.dev

5年ぶりのオフライン開催

Kotlin Fest 2024は今回5年ぶりのオフライン開催となりました。会場はベルサール渋谷ファーストの2Fを貸し切って、2つのセッションルームと1つのスポンサーブース兼フリースペースという形の大きな会場での開催でした。

スポンサーブースの紹介

エブリーは8年前からDELISH KITCHENアプリでKotlinを採用しています。Kotlin採用に対する振り返りを当時採用したCTO自らが綴っているのでご覧ください。

tech.every.tv

いつもKotlinコミュニティの恩恵を受けている我々もコミュニティのさらなる盛り上がりに貢献していきたく、スポンサーとして協賛させていただき、ブースも出展しました!

ブース

エブリーでは、弊社が提供するDELISH KITCHENのサービスをイメージしてブースの雰囲気を作っています。今回も多くの方からDELISH KITCHENを使っていますとの声をかけていただき、実際に使っていただいている方の声を聞ける貴重な機会で僕たち開発者もパワーをもらえました。

ノベルティ

今回は以下のようなノベルティを用意させていただきました。

  • クリアファイル
  • 会社とサービスのステッカー
  • DELISH KITCHENグッズ

DELISH KITCHENグッズに関してはXフォローでの抽選プレゼントキャンペーンを行いました。DELISH KITCHENグッズに関してはたくさんの商品があるのですが、その中でも人気のある商品を中心に5つ準備させていただきました。 用意してた全てのグッズがなくなるほど好評で多くの方に参加していただけました!

アンケート

今回、アンケートでは『KotlinのLinter、なにを使ってる?』と題して回答をしてもらいました。また、シールの色でKotlinをAndroid開発で使っているか、サーバーサイドで使っているかがひと目でわかるような工夫もあり、参加者が楽しんでいただけるような内容を考えました。回答いただいた多くの皆様、ありがとうございました!

最終結果はこちら...!

  • Androidエンジニア
    • 👑1位: ktlint
    • 2位: Android Lint
    • 3位: detekt
  • サーバーサイドエンジニア
    • 👑1位: detekt
    • 2位: ktlint
    • 3位: その他(Konsist, CheckStyle)

全体ではktlintが1位となり、枠をはみ出すほど多くの方の回答が集まっていました。一方でサーバーサイドエンジニアの中では、ktlintとdetektを使っている方の割合がほぼ同じ僅差という面白い結果となっています。 ブースに来てくださる方はAndroidエンジニアの方が割合としては多いですが、サーバーサイドでKotlinを活用している方も多くいらっしゃり、両プラットフォーム上でのKotlinの盛り上がりを肌で感じました。

各社スポンサーブースの様子

スポンサーブースは、2つのセッションルーム間で開催され、オープニング前やセッション間での休憩中に多くの人で賑わっていました。こういった光景を見られるのもオフラインならではです。

各社のブースもそれぞれの会社の特色がノベルティや出し物から出ており、スタンプラリーをしながら楽しませてもらいました。

特にエムスリーさんのブースではKotlinのモチーフである『トリ』に関連して、『エンジニアトリ診断』を受けることができ、Kotlinや開発に関するいくつかの質問に答えることでトリタイプが診断されます。僕の結果は...『はやぶさ』タイプでした!

セッションの紹介

今回発表されたセッションの中から気になったものをいくつかまとめさせていただきました。

パフォーマンスと可読性を両立:KotlinのCollection関数をマスター

発表者: Masayuki Sudaさん

fortee.jp

こちらのセッションでは、Kotlin の Collection 関数を有効活用する方法を紹介されていました。 Collection 関数の中には変換を行う Map や Zip、フィルタリングを行う filter や partition、グループ化を行う groupBy、部分取得を行う slice など多種多様な関数が用意されていますが、一つ一つ具体的なソースコードと処理結果を説明してくださいました。 その中で、+ 演算子や - 演算子を扱い要素の追加や削除を行う場合は新しいリストが生成されるためメモリが消費しやすい、windowed を使うと計算量が多くなり、パフォーマンスに影響が出るなど、細かい課題や問題点まで説明してくださっていた点が印象的でした。

また最後に Collection、Sequence、for の3 つでパフォーマンスの最適化の観点で、

Collection … 非常に優秀。即時評価を行い、すべての要素を評価する仕組みで、リストが大きい場合はパフォーマンスに難有り。 Sequence … 遅延評価を行う仕組みで、大規模なコレクションに対して複数の処理を行う場合に向いている。 for … 最速。

といったまとめをしてくださっていました。プログラムを作成するうえではパフォーマンスは重要なため、常に説明してくださった観点は意識しようと改めて感じました。

なお、この記事内では一部の関数のみ抜粋して紹介しましたが、Collection 関数自体はまだまだ多く提供されており、使ったことがないものも多々あるため、これを期に勉強し直したいと強く感じました。

withContextってスレッド切り替え以外にも使えるって知ってた?

発表者: T45Kさん

fortee.jp

こちらのセッションでは、withContext をスレッドの切り替え以外でも使うことができる、という内容を紹介されていました。

Coroutines では API の処理は withContext(Dispatchers.IO)、計算量が多い処理は withContext(Dispatchers.Default)、UI 関連の処理は withContext(Dispatchers.Main) と、スレッドの切り替えのために使用することがあり、私自身このためだけに使用するものと認識していましたが、実際は CoroutineContext を切り替えるものであり、スレッドを切り替える以外の用途で利用されるものと紹介され、しっかりと理解していないまま利用していると反省するきっかけになりました。

なお、コンパイル時に Context の検査が行われる、Coroutines でなくとも使用ができる Context Parameter というものが Kotlin 2.1 で導入予定となっているそうで、これから注目して追っていきたいと思います。

KotlinのLinterまなびなおし2024

発表者 nyafunta9858さん

fortee.jp

こちらのセッションでは、KotlinのLinter導入へのモチベーションや各Linterについての特徴について解説されていました。

アーキテクチャやコードルールをLinterを用いてプロジェクト内で統一するモチベーションの一つとして、メンバーの入れ替わりが挙げられていました。ルールを統一することで、メンバーの入れ替わりに影響されずコードの品質を担保できます。

everyはエンジニアが様々なプロダクトを経験することができる環境ですので、Linterの運用は強力な武器になると最認識しました。 またブースではどのLinterを使用しているかアンケートを取りました。結果としてはktlintが多く、次点でdetectでした。この発表やアンケート結果を弊社プロダクトに活かしていければと思います。

Jetpack Compose: 効果的なComposable関数のAPI設計

発表者: haru067さん

fortee.jp

こちらのセッションでは、主にComposable関数の引数の注意点とテクニックについて紹介されていました。

引数はそのコンポーネントの再利用性や拡張性を考えて書く必要があり、その中で有効なテクニックとして以下が紹介されていました。

stateはコンポーネント内で閉じてもよいなら閉じる。他コンポーネントと状態を共有したい場合は親に委ねる。

引数はパフォーマンスの観点から基本的にフラットに記述する。データクラスを用いると、他画面との兼ね合いで無駄な再描画を引き起こす可能性がある。引数が多くなった場合はクラスへ切り分けることを考慮する。

デフォルト引数は安易に設定しないように気をつけ、値をつど考えることが有益な場合は設定しないようにする。

など開発の際に誰もがどうしようか考える内容に触れられており、大変面白かったです。

特に引数をフラットに書く話は、パフォーマンスに直結するので気をつけようと思います。

最後に

最後になりますが、Kotlin Fest 2024の運営の皆さん、カンファレンスの運営をしていただき本当にありがとうございました!

また、参加者の皆さん、カンファレンスへの参加お疲れ様でした。

弊社も当日多くのエンジニアが参加し、セッションを聞きながらKotlinコミュニティから刺激を受けるいい機会となりました。今後もイベントやこういったスポンサー活動を通じてKotlinコミュニティに貢献していきたいと思います!

今回参加できなかった皆様もぜひ来年は参加してみてください。

エブリーでは、ともに働く仲間を募集しています。

テックブログを読んで少しでもエブリーに興味を持っていただけた方は、ぜひ一度カジュアル面談にお越しください!

corp.every.tv

最後までお読みいただき、ありがとうございました!

8年前にKotlinを採用してたくさん恩恵を受けた話

この記事は every Tech Blog Advent Calendar 2024(夏) 21 日目の記事です。

はじめに

こんにちは、エブリーでCTOをしている今井です。
Kotlin Fest 2024 の開催がいよいよ明日に迫ってきました。
エブリーでは8年前にDELISHKITCHENのアプリを作り始めた時からKotlinを使っており、
今回KotlinFestを通じて、Kotlinコミュニティに貢献できることを嬉しく思っております。

このブログでは、Kotlinを採用した当時の状況や、採用して良かったことに関して振り返ってみたいと思います。

Kotlinの採用

FirstCommitのスクリーンショット
改めてコミットを見たところ、Kotlinを初めて導入したのはなんと2016年のFirstCommitからでした。
当時のバージョンは1.0.3で、その時はまだGoogleの正式なサポートもまだ発表される前1で、
Androidアプリ開発においてまだまだJavaが主流。かつこんなにKotlinが急激に浸透するとは思ってもいませんでした。

また、開発を進めるにあたってもKotlinの導入事例はとても少なく、ちょっとしたことでも調べるのに苦労したことを覚えています。

当時のコードを振り返ると、たくさん拡張関数作って、ActivityやFragmentをシュッと書きたいという
自分なりにKotlinらしく書くことを模索した形跡がありました。

FragmentUtil.kt

fun SupportFragment.inflate(layoutResId: Int, inflater: LayoutInflater?,
                            container: ViewGroup?): View? =
        inflater?.inflate(layoutResId, container, false)

fun Fragment.inflate(layoutResId: Int, inflater: LayoutInflater?,
                     container: ViewGroup?): View? =
        inflater?.inflate(layoutResId, container, false)

MainFragment.kt

class MainFragment : AbstractFragment() {

    companion object {
        @JvmStatic fun newInstance() = MainFragment()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? =
            inflate(R.layout.fragment_main, inflater, container)
}

これ自体は今思うとまだまだ未熟な書き方も多く恥ずかしい部分も多いですが、
Kotlinを採用したことでの恩恵も多く受けたと思います。

Kotlinの恩恵

Null-Safety

一番の恩恵は確実にNull-Safetyにありました。
当時Androidは自分一人での開発だったためレビューもしてもらえない環境かつ、
その時点での自分のAndroid開発経験も半年ほどと未熟だった中で、
リリース当初からCrashFreeRateが99.9%という高いスコアを出せていたのは、
Kotlinを採用していなければ実現できなかったと思います。

シンプルで短く書ける

また、シンタックスがシンプルで短く書けることも初期の開発速度に寄与していたと思います。
上記で紹介した拡張関数やLambdaなどは使えるところは意地でも使うくらい、ハマってました。
Null-Safetyではどうしてもその中身のチェックに記述が増えがちなところあると思いますが、
SmartCastなどにより、書きやすさを維持したままより安全なコードが書けるも良かったです。

書いていて楽しい

Kotlin自体の機能ではないですが、上記の恩恵などから、書いていて楽しかったというのも、
今思うと恩恵として大きかったと思います。まだまだ未知なものを自分の手で開拓していく感じも面白かったです。

まとめ

エブリーではAndroidアプリの開発当初からKotlinを採用し、Kotlinに支えられてきました。
同様に弊社のあらゆるエンジニアは多くの技術やオープンソースに支えられています。
これらを盛り上げる技術コミュニティに支援をすることは、会社にとってもの技術投資でもあると考えています。
今回のKotlinFestをはじめ、今後も技術コミュニティにも積極的に支援していきたいと考えております!

余談: フルKotlinのアプリでストア総合一位はDELISHKITCHENが初?

Kotlinが採用されたGoogleI/O 2017には現地で参加していたのですが、
ちょうどそのとき日本のストアで総合1位になってました。
今日本で1位のアプリはフルKotlinだというのをGooglerに話したところ、
フルKotlinとか聞いたことない / 多分ランキング初めてなんじゃないか?
と言っていただいたので、個人的には勝手に弊社のアプリが初めてだと思ってますw

GoogleI/O 2017の時のストアのスクリーンショット

Android プロジェクトの KSP 化を検討するにあたって

この記事は every Tech Blog Advent Calendar 2024(夏) 20 日目の記事です。

はじめに

こんにちは、DELISH KITCHEN でクライアントエンジニアを担当している岡田です。
今回は KSP 化について執筆させていただきます。

概要

Kotlin でより効率的な開発を行う上で、アノテーション処理は欠かせない要素です。アノテーションを利用することで、定型的なコードを自動生成したり、コンパイル時にコードの検証を行ったりすることができます。

Kotlin のアノテーション処理といえば、従来は kapt が主流です。しかし、近年ではより高速でKotlinの言語機能を活かせる KSP が主流になりつつあります。

この記事では、kapt と KSP について調べ、 Android プロジェクトの KSP 化を検討するにあたって必要な情報を記述します。

kapt について

kapt (Kotlin Annotation Processing Tool) を使用すると、 Java アノテーションプロセッサを使用して Kotlin コードを処理できます。
Room などの Android 開発でよく使用される、多くのライブラリと連携しています。

しかし、 Kotlin ファイルから Java アノテーションプロセッサが読み取れるようにするためには Java スタブを生成する必要があります。
実際に生成された Java スタブ は build/generated/source/kapt/ で確認することができます。
例えば以下は実際に DELISH KITCHEN で Hilt によって生成される、 ArticleListActivity のインスタンスに依存性を注入するためのコードです。

ArticleListActivity_GeneratedInjector.java

@OriginatingElement(
    topLevelClass = ArticleListActivity.class
)
@GeneratedEntryPoint
@InstallIn(ActivityComponent.class)
public interface ArticleListActivity_GeneratedInjector {
  void injectArticleListActivity(ArticleListActivity articleListActivity);
}

.*_GeneratedInjector.java@AndroidEntryPoint@Inject など、依存性を注入する必要がある全箇所に対して生成されます。
この例のような Java スタブが、 Hilt に関してのみでもプロジェクト内で相当な数生成されています。
Java スタブの生成は高コストのオペレーションのため、処理速度が遅く、ビルド速度に大きく影響してしまうのです。

また 2024/6/20 時点で kapt はメンテナンスモードに入っています。
「既に新しい機能が実装されることはない」と明記されており、 KSP の使用が推奨されています。

KSP がサポートされていないライブラリを用いたいなどの理由がない限りは、 kapt ではなく KSP を使用する方が良いでしょう。

KSP について

KSP (Kotlin Symbol Processing) は、 Kotlin コードを直接処理するためのアノテーション処理ツールです。
kapt とは異なり、 Kotlin コンパイラのプラグインとして動作するため、 Java コードへの変換が不要です。
つまり kapt のように Java スタブを生成する必要がなくなり、処理速度が大幅に上昇し、ビルド速度も速くなります。

Googleが公開しているGithubによると、「 kapt と比較すると、 KSP を使用するアノテーションプロセッサは最大 2 倍高速に実行できる」そうです。
また Kotlin の公式ドキュメントでは、コード生成に 7.5 倍ほどのパフォーマンス差があることも確認されています。

For performance evaluation, we implemented a simplified version of Glide in KSP to make it generate code for the Tachiyomi project. While the total Kotlin compilation time of the project is 21.55 seconds on our test device, it took 8.67 seconds for kapt to generate the code, and it took 1.15 seconds for our KSP implementation to generate the code.

仕組みとして、 Kotlin プログラムを Kotlin の文法に沿ってシンボルレベルでモデル化しているそうです。
KSP は Kotlin プログラムの構造を、クラスや関数などの構成要素単位で処理することができます。
Kotlin プログラムの構造をより抽象化された形で捉えて処理することで、アノテーション処理などのタスクを効率的に行います。
ただし、 if 文や for ループなどの制御構文にはアクセスできず、細かい制御フローの解析には向いていません。

kapt から KSP へ移行する方法は、公式ドキュメントを参照ください。
また KSP の最新のリリースはこちらから確認できます。
今後のロードマップには、マルチプラットフォーム対応やパフォーマンス改善も挙げられています。

実際の速度比較

では実際に KSP 化すると、 Kotlin のタスクはどの程度速度が上がるのでしょうか。
今回は簡単に、 Room でデータを Insert するだけの簡単なアプリを作成して比較しました。
測定方法は Android Studio の Build > Clean Project 実行後、 Build > Rebuild Project を実行し、 Build Analyzer にて測定しました。 データはそれぞれ 10 回ずつ測定し、画像は平均に一番近いデータを添付しています。

kapt KSP
1.9s 1.4s

結果として、およそ 0.5s ほど KSP の方が早かったです。 kapt で生成された Java スタブとして、以下のファイルが確認できました。

  • AppDatabase_Impl.java
  • UserDao_Impl.java

これらは Room の Database と Dao 関連のスタブです。たった 2 ファイルの生成ですが、如実な差を確認することができました。
Room や Hilt を導入しているプロジェクトでは、大幅な速度上昇が見込めそうです。

KSP の注意点

kapt と KSP の併用する際は注意が必要です。

Room などの主要なライブラリは KSP に対応していますが、 KSP に対応していないライブラリも存在します。
こちらで現在サポートされている主要ライブラリを確認することが可能です。
例えば 2024/6/20 時点で、 Auto Factory は KSP をサポートしていません。
またプロジェクトのライブラリ更新が滞っており、そちらを先に対応しないと KSP 化できないという場合も想定できます。
プロジェクトでこれらのような KSP に未対応のライブラリを導入している場合は、一部のみを KSP 化し、 kapt と併用する形になるでしょう。

しかし kapt と KSP の併用は、ビルド時間が増加することがあります。
これは同じアノテーションプロセッサを kapt と KSP の両方で実行する場合に処理が重複してしまうためです。
高速なアノテーション処理を期待して KSP を導入したのに、逆に処理が遅くなるという事態に陥ります。

まとめ

この記事では、 Kotlin のアノテーション処理ツールである kapt と KSP について記述しました。

kapt は Java のアノテーションプロセッサを Kotlin で利用できるツールですが、 Javaスタブ の生成が必要なためビルド時間が長くなるという欠点があります。
また、現在メンテナンスモードに入っているため、今後 KSP への移行が推奨されています。

KSP は Kotlin コードを直接処理できるため、 kapt よりも高速にアノテーション処理を行うことができます。
公式ドキュメントによると、 kapt と比較して最大2倍、コード生成では 7.5 倍ほどのパフォーマンス差があるそうです。

KSP 化を進める際は、 KSP に対応していないライブラリが存在することに注意が必要です。
そのようなライブラリを使用している場合は、 kapt と KSP を併用する必要がありますが、ビルド時間が増加する可能性があるため注意が必要です。

おわりに

Kotlin Fest 2024 まで、あと 2 日! https://www.kotlinfest.dev/

株式会社エブリー は、ひよこスポンサー として Kotlin Fest 2024 に参加します。 ぜひ、ブースでお会いしましょう!

LiveData を Kotlin Coroutines Flow に移行した話

この記事は every Tech Blog Advent Calendar 2024(夏) 19 日目の記事です。

はじめに

こんにちは、DELISH KITCHEN でクライアントエンジニアを担当している kikuchi です。

Kotlin Fest 2024 の開催が近づいてきましたので、今回は折角の機会ですので Kotlin に関わる話として
DELISH KITCHEN で一部の処理を LiveData から Kotlin Coroutines Flow に移行した話をまとめてみたいと思います。

移行を考えた背景

現状 DELISH KITCHEN ではアーキテクチャに MVVM (Model View ViewModel) を採用しており、ViewModel で更新された LiveData を
View が observe するという一般的な実装となっていますが、今回 Flow を調査する過程で

  • オペレータ (map など) が使用できる
  • Null 安全性が保証される
  • 既に通信周りを Coroutines で実装していたため、Flow に移行しやすい
  • ワンショット通知を無理やり LiveData で実現していた箇所を適切な方法に修正ができる (SharedFlow の利用)
  • 新しい技術の習得

というメリットを感じ、今回新規で実装する箇所から徐々に Flow を採用することを決断しました。
特に新しい技術の習得というのはエンジニアにとって成長に繋がる良い機会ですので、積極的に取り入れたいと考えました。

LiveData と Flow の違いについて

早速ですが、本項目では LiveData と Flow の違いを細かくまとめていきます。

LiveData について

LiveData とは Android Jetpack の一部であり、Android のライフサイクルに対応した監視ができる仕組みです。

監視のタイミングですが、オブザーバーのライフサイクルの状態が STARTED か RESUMED の場合のみ LiveData の変更通知を受け取ることができます。
オブザーバーを Activity や Fragment のライフサイクルと紐づけている場合、onStarted や onResume の場合 (画面がアクティブな状態) でのみ通知を受け取ることができ、
onPause や onStop の場合 (画面が非アクティブな状態) では通知を受け取ることができません。
また observe の処理さえ定義しておけば自動的に変更通知を受け取れるようになります。
つまりは実装上で明示的にオブザーバーを開始・終了する必要は無く、また画面がアクティブであるかの判定も不要ということになります。

上記をふまえて、オブザーバーを Activity のライフサイクルに紐づけてデータを監視する簡単な実装例を載せたいと思います。

// ViewModel 側の実装
class TestViewModel {

    private val _testData = MutableLiveData<Int>()
    val testData: LiveData<Int> = _testData

    fun updateTestData() {
        _testData.postValue(1)  ...①
    }
}


// View 側の実装
class TestActivity : AppCompatActivity {

    private val viewModel: TestViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        viewModel.testData.observe(this) { data ->  ...②
            data ?: return@observe  ...③

            // 監視しているデータの更新通知を受け取ったら実行する処理
            updateView(data)
        }
    }
}

ViewModel 内部では更新可能な MutableLiveData を更新し、連動して外部に公開している LiveData も更新されるように実装することで、
① のように ViewModel 内で値を更新すると、② の observe の処理が自動的に発火する挙動となります。
実装例を見て分かる通り、明示的にライフサイクルに応じて監視を開始・終了する処理はしていません。

ここで 1 つ注意点があり、LiveData は Java で実装されているため Null 安全性は保証されておらず、Null が設定されてしまう可能性があるため、
念の為 ③ のような Null チェックが必要となります。

Flow について

Flow とは Kotlin Coroutines 上で非同期にデータを取り扱うための仕組みです。

Flow には常に最新の値を保持する StateFlow と、replay パラメータで設定した数分の過去の値を保持できる SharedFlow が存在しますが、
今回は StateFlow を利用してオブザーバーを Activity のライフサイクルに紐づけてデータを監視する実装例を載せたいと思います。

// ViewModel 側の実装
class TestViewModel {

    // MutableStateFlow では初期値が必要
    private val _testData = MutableStateFlow<Int>(0)
    val testData: StateFlow<Int> = _testData

    fun updateTestData() {
        _testData.value = 1  ...①
    }
}


// View 側の実装
class TestActivity : AppCompatActivity {

    private val viewModel: TestViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        lifecycleScope.launch {  ...②
            repeatOnLifecycle(Lifecycle.State.STARTED) {  ...③
                viewModel.testData.collect { data ->  ...④
                    // 監視しているデータの更新通知を受け取ったら実行する処理
                    updateView(data)
                }
            }
        }
    }
}

LiveData と同様、ViewModel 内部では更新可能な MutableStateFlow を更新し、連動して外部に公開している StateFlow も更新されるように実装することで、
① のように ViewModel 内で値を更新すると、オブザーバー側で通知を受け取ることができます。
ただし LiveData と異なる点は、② のように必ず lifecycleScope.launch のスコープ内であることを明示する必要があり、③ のようにどのライフサイクルで通知を受け取るか
明示する必要があります。実装例だと画面がアクティブな場合に受け取れるよう repeatOnLifecycle(Lifecycle.State.STARTED) を指定しています。
(上記 ② と ③ の指定をすることで、LiveData のケースと同じ契機で更新通知を受け取ることができます)
データを受け取る処理は ④ のように collect で受け取ります。

Flow は LiveData と違い Kotlin で実装されているため Null 安全性が保証されており、Null チェックは不要となります。
また、具体的な例は割愛しますが Flow はオペレータを使用できるため、直接 Flow 型のデータを操作する場合は filter や map などを使用することができます。
こちらは LiveData に無い Flow の強みと言えます。

LiveData と Flow の比較

今回は簡単な実装例のみ載せましたが、ほぼ同じような挙動にすることが可能だと分かりました。
ただ細かい部分で違いがありましたので、違いを一覧でまとめてみたいと思います。

LiveData Flow
実装の複雑さ やや複雑
Null 安全性の保証 されていない (が実装でカバーできる) されている
オペレーター使用可否 使用不可 使用可能
監視タイミングの管理コスト 低 (Android のライフサイクルと連動) 中 (実装で明示する必要あり)
KMP での使用可否 不可 可能

移行にあたって苦労した点

LiveData と同じ用途で実装しようとするとどうしてもコードの記述量が増えてしまいましたが、今回は紐づけるライフサイクルを変更する必要が無いため、
以下のように拡張関数を定義することでコードの煩雑さを解消しました。

// 拡張関数
fun <T> Flow<T>.observe(viewLifecycleOwner: LifecycleOwner, action: (T) -> Unit) {
    viewLifecycleOwner.lifecycleScope.launch {
        viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
            collect { action(it) }
        }
    }
}


// オブザーバー側
class TestActivity : AppCompatActivity {
    override fun onCreate(savedInstanceState: Bundle?) {
        viewModel.testData.observe(this) { data ->
            updateView(data)
        }
    }
}

また、移行当初は Android Jetpack Lifecycle ライブラリで使用されており、かつ現在は Deprecated となっている lifecycleScope.launchWhenStarted で定義していたことで、
オブザーバー側が STOPPED となってもコルーチンがキャンセルされず停止された状態のままとなっており、リソースが浪費されている問題が発生していました。
こちらは前述した viewLifecycleOwner.repeatOnLifecycle を使用することで、意図通りオブザーバー側が STARTED 状態になる度に実行し、STOPPED になる度にキャンセルされる
挙動となったため回避できたのですが、Deprecated は常に意識して日々改修していく重要さを改めて痛感しました。

まとめ

LiveData は Android のライフサイクルが考慮されているなど Android アプリに対して最適化された作りとなっており、対して Flow はオペレータの使用が可能である、
Null 安全性が保証されているなど、より Kotlin の恩恵を受けることができるため、どちらを採用する場合でもメリットがあります。
そのため開発するアプリの規模や実現したい機能、学習コストなどからどちらを採用するか検討すると良いかと考えています。

近年サーバーサイド Kotlin の導入事例も増えてきていますので、アプリエンジニアでもサーバー開発をすることを見越して Kotlin の標準 API である Flow を採用する、
という考え方もあるかもしれません。
エブリーではこれからも職種にとらわれずいろいろな事に挑戦できる環境を作って行きたいと考えています。

今回紹介した内容が少しでも皆さまのお役に立てれば幸いです。

おわりに

Kotlin Fest 2024 まで、あと 3 日!
https://www.kotlinfest.dev/

株式会社エブリー は、ひよこスポンサー として Kotlin Fest 2024 に参加します。
ぜひ、ブースでお会いしましょう!