iOS標準のセミモーダルを任意のサイズで表示するTips

前書き

こんにちは。withでiOSエンジニアをしている長尾です。

今回はiOSセミモーダルUIを、任意のサイズで表示する方法をご紹介します。

TL;DR

iOS標準のセミモーダルの背景を透過して、その上にビューを配置すれば、任意のサイズのセミモーダルを表示できます!

今回実装したいUI

今回実装したいUIはこんな感じです。

  • 直前に表示されていたコンテンツの一部を覆うセミモーダル
  • 背景のコンテンツは触れない
  • 引っ張って閉じられる
  • セミモーダルのサイズはコンテンツのサイズによって可変させる

ポイントは、最後の「セミモーダルのサイズはコンテンツのサイズによって可変させる」です。

何が難しいの?

iOSセミモーダルUIを実現したい場合、一番簡単なのは UIViewController.modalPresentationStyle.pageSheet に設定することです。

しかしこの方法だと、表示するセミモーダルビューのサイズを任意のサイズに変更することができません。常にUIViewControllerのルートのビューが画面いっぱいに(セミモーダル)表示されてしまいます。

別の案として、iOS15から使える UISheetPresentationController.detents を使用することで高さを可変させることができますが、detentsに指定できるのは .large().medium() のみで、コンテンツビューに合わせて任意のサイズにすることはできません。またiOS15未満をサポートしているプロジェクトでは使えません。

またOSSである SCENEE/FloatingPanelを使うという手もあります。こちらのOSSを利用すればコンテンツサイズによってハーフモーダルのサイズを可変させる事ができます。しかし、OSSを追加で導入しないといけないというハードルはありますし、遷移元の画面側からFloatingPanel、およびViewを生成する必要があり、遷移元の記述が複雑になりがちです。(専用のControllerを書くという手はありますが)

このように、サイズ可変のセミモーダルを表示するのは一筋縄ではいきません。そこで今回は、iOS標準のセミモーダルUIを、コンテンツサイズによって任意のサイズで表示する方法 をご紹介します。

実装

0. 完成イメージ

今回はサンプルとして、↓のような課金画面を作成してみます。

ビューのサイズによって、セミモーダルのサイズがうまく調整されています。

1. 表示元の実装

セミモーダルを表示する側の実装を見ていきます。

表示側としては、普通にUIViewControllerをセミモーダルで表示するようにUIViewController.present(_:, animated:)を利用します。

今回セミモーダルで表示する画面は PaymentViewController というUIViewController継承のクラスです。 普通にセミモーダル表示するように modalPresentationStyle.pageSheet (セミモーダル)に設定して表示するだけです。

@IBAction private func tappedCheckoutButton(_ sender: Any) {
    let paymentVC = PaymentViewController()
    paymentVC.modalPresentationStyle = .pageSheet
    paymentVC.tappedCancelHandler = {
        paymentVC.dismiss(animated: true)
    }
    present(paymentVC, animated: true)
}

2. 表示対象のView(PaymentView)の実装

表示対象のViewは、普通にAutoLayoutを利用して組みます。

幅によって高さが可変するように、制約を設定しましょう。

final class PaymentView: UIView {

    var tappedCancelHandler: (() -> Void)?

    // MARK: - initializer

    override init(frame: CGRect){
        super.init(frame: frame)
        loadNib()
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
        loadNib()
    }

    convenience init() {
        self.init(frame: .zero)
    }

    private func loadNib(){
        let className: String = .init(describing: type(of: self))
        let view = Bundle.main.loadNibNamed(className, owner: self, options: nil)?.first as! UIView
        view.frame = self.bounds
        self.addSubview(view)
    }

    @IBAction private func tappedCancel(_ sender: Any) {
        tappedCancelHandler?()
    }
}

xibを読み込むコードが大部分を占めています。特別な処理は必要ありません。

3. 表示対象のViewControllerを作成する

今回の話の肝はここです。

ViewControllerの背景色は透過に設定し、表示したいViewを画面下部にくっつけるような制約を設定します。

非常に単純ですが、これで評されるコンテンツのサイズによって、セミモーダルのサイズを可変させることができるようになります。

final class PaymentViewController: UIViewController {

    var tappedCancelHandler: (() -> Void)?

    override func loadView() {
        self.view = UIView()

        // 背景は透過する
        view.backgroundColor = .clear

        // 背景タップ時、キャンセル扱いにする
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tappedBackground)))

        // セミモーダル表示するビューを生成して、addSubviewする
        let paymentView = PaymentView()
        paymentView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(paymentView)
        view.addConstraints([
            paymentView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            paymentView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            paymentView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])
        // PaymentView自体のタップは処理しない
        paymentView.addGestureRecognizer(UITapGestureRecognizer())

        // 角丸設定
        paymentView.layer.cornerRadius = 10
        paymentView.layer.masksToBounds = true
        paymentView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]

        paymentView.tappedCancelHandler = { [weak self] in
            self?.tappedCancelHandler?()
        }
    }

    @objc private func tappedBackground() {
        tappedCancelHandler?()
    }
}

ビューの角丸はなくなってしまうので、自身で cornerRadiusmaskedCorners を設定してあげる必要があります。

課題

コンテンツのサイズによってセミモーダルのサイズを変える、というこの実装には、コンテンツサイズが画面サイズを越えてしまう場合に、画面の表示領域外にコンテンツがはみ出してしまうという課題があります。 必要に応じてUIScrollViewに載せたりしないといけないのですが、おそらく制御はまま大変かと思います。 採用するかは状況に応じてご判断ください。

ソースコード

今回のソースコードは↓のリポジトリにアップしてあります。もしお手元で動かしてみたい方はご利用ください。

https://github.com/zrn-ns/FlexibleSemiModalUISample/

DroidKaigi 2022 参加レポート

こんにちは。with で Android エンジニアをしている 石田(@maxfie1d) です。 先日の 2022 年 10 月 5 日〜2022 年 10 月 6 日の 2 日間 DroidKaigi 2022 に参加しましたので参加レポートをお届けしたいと思います。

DroidKaigi 2022 とは

DroidKaigi 2022 会場へ到着!

DroidKaigi はエンジニアが主役の Android カンファレンスで、2015 年から毎年開催されています。2022 年はオフライン/オンラインで開催されました。

素敵な参加者カードをもらって入場!

入場してさっそく 「Welcome talk」 が始まりました。その中で DroidKaigi の公式アプリのコントリビューターが映し出されたのですが、私も載っていました。嬉しい...! そして600人超のオフライン参加者がいるということでとても驚きました(Android エンジニアは市場でも少ないと言われているので)。

スポンサーブースの様子

DroidKaigi ではセッションに加えて スポンサー企業様のブースがあり様々な展示、企画、ディスカッションを楽しむことができます。ノベルティも素敵なものばかり。スタンプラリーのスタンプを押してもらいながら全てのブースを楽しく回らせていただきました!

1 日目

印象に残ったセッションや DroidKaigi 中に頂いたランチを紹介したいと思います。

Jetpack Compose で Material Design 3 (Yuki Anzai)

droidkaigi.jp

Material 3 で導入された新しい概念や Material 2 からの実践的な移行手順等についてのセッションでした。

規模の小さなアプリであれば一括で Material 3 に移行することも可能だが、規模の大きなアプリ(with も含まれる)の場合はコンポーネント単位で移行するのがよいと紹介されていました。Material 2 → Material 3 の移行は大変骨が折れる作業になる予感がしていますが、しっかりと時間を確保して取り組みたいと思います。

Material 3 は Material 2 よりも実用的になった一方で、Deisgn tokens といった新規の概念が沢山あるので同時に理解度を上げていきたいと思います。

本セッションの翌日には androidx.compose.material3:material31.0.0-rc01 が出たので Material 3 もいよいよだなという感じがしています。

ランチ

会場近くのお店で石焼ビビンバをいただきました!

「周りの余計なものが映らないように寄りで撮ると綺麗に写るよ」と最近入社した同僚のエンジニアが教えてくれました。上手に撮れているでしょうか?

Context Receivers に思いを馳せる (uzzu)

droidkaigi.jp

Kotlin 1.6.20 にプロトタイプで登場した context receivers についてのセッションでした。

Kotlin が context receivers にたどり着くまでの歴史的経緯や、Android 開発におけるユースケースなどが詳しく紹介されました。登壇者の指摘の通り、context receivers は 堅実な言語仕様を保ってきた Kotlin にしては非常に自由度が高く乱用ができてしまうなといった印象で、この先安定版としてどのような形に落ち着くのか非常に興味が湧きました。

Context receivers を プロダクト開発で使える日が楽しみです。

2 日目

プロダクトで安全に DataStore 移行する (Go Takahana)

droidkaigi.jp

SharedPreferences を DataStore に移行する実践的な方法についてのセッションでした。

SharedPreferences との違いや、移行時のハマりポイントなど非常に網羅的で納得感のある解説でした。DataStore へ移行するとデータアクセス部分のシグネチャが変わるため変更箇所が大きくなりがちです。また DataStore へのマイグレーションが実行されると SharedPreferences のオリジナルのデータが削除されるという仕様には注意が必要です。登壇者の提案通り 「1. 一度に全てのキーを移行せず少しずつ移行する」「2. 切り戻しリスクの低いリリースで移行する」 の 2 点がポイントになると感じました。

ランチ

2 日目は会社で用事があったため、会場を一時的に離れ恵比寿で握り寿司をいただきました。

DroidKaigi 参加中に食べるお寿司は美味しいです

Deep dive into Jetpack Compose Text (Seigo Nonaka)

droidkaigi.jp

Android の Text についてのセッションでした。

Text は最も基本的な UI の 1 つですが、その実装は奥深いものです。Compose の Text で今後追加される API の紹介があったのですが、今後も TextView にない機能が続々と追加されるのだろうなと思いました。 ちなみに、従来の TextView と Compose の Text において Layout から下の実装は共通のものが使われているそうです。

おわりに

初めてのオフラインでの DroidKaigi 参加だったのですが、広い会場、たくさんのスポンサーブース、かっこい登壇者の方々...。とても感動しました。2 日目には Meetup(DroidKaigi 参加者同士の交流の場)に参加させてもらったのですが、Android エンジニア同士で有意義な意見交換をすることができ 2 時間があっという間に感じました。

来年の DroidKaigi にはぜひ登壇者として参加したいなと思っています!

来年こそは DroidKaigi のステージで登壇するぞ!

エンジニアのリモートデスク環境を調査してみた


こんにちは!
withでiOSエンジニアをしている吉澤です。

リモートワークが普及してから数年経ちましたね。
現在withでは、出社したい人は出社し、リモートで仕事をしたい人はリモートで仕事をするという自由な形態をとっており、ほとんどのエンジニアがリモートで仕事をしています。

そうなってくると重要なのが、自宅での開発環境です。
withのエンジニアはリモートワークを快適に過ごすために、どのような工夫をしているのでしょう?
今回はiOSエンジニアチームの皆さんにご協力いただき、調査してみます!

一人目は社内でiOSエンジニア歴最長のMさんにお聞きしました!

Mさんのデスク。インタビュー日は出社されていたので、オフィスでのデスクまわりの写真をいただきました。

よろしくお願いします!

早速ですが、Mさんはオフィス、自宅、実家と、色々な場所で仕事されていますよね。

それぞれどのような環境でお仕事されているのでしょうか?

モニターについては、どこで仕事をする場合も一緒で、メインのモニターを真ん中に置き、MacBookをサブモニターとして横に置いて使っています。

デスクについては色々試していて、
たとえば実家では、長めの木の板を膝の上において、その上にキーボードとトラックパッドをおいて作業していた時期がありました。

まさかのデスクを使わないスタイルですか!

机が高いと肩が凝ってしまって・・・。なるべく低くしようとした結果ですね!

たしかに、机が高いと肩こりに悩まされますよね。
とはいえ、膝の上にキーボードとトラックパッドを置く発想はなかったです!

椅子はどのようなものを使っているんですか?

椅子については、ペダルを漕ぎながら作業できる椅子などを使っていた時期もありました!
でも、今は一周まわって普通の椅子を使っています。
やっぱ背もたれがあったほうが良いなって(笑)

背もたれは大事ですね!

あとはイヤホンについてもこだわりを持っていますね。
昔のiPhoneについてきた有線のイヤホンを使ってます。

ワイヤレスではないんですね!珍しい気がします。

ワイヤレスだと、ミーティングが長い時にバッテリーを消耗しちゃうじゃないですか。
かといってイヤホンせずにスピーカーを使うと、他の人と同時に自分も話した場合に、ノイズキャンセリングで声が消されてしまうことがあって。

音も良いし、有線のイヤホンが一番良いなと思って使ってます。

なるほど。ミーティングだとその場から動かないし、長時間になることもあるので、有線のイヤホンを使うのもアリかもしれません!

 

二人目は最近AndroidチームからiOSチームに来てくださったNさんにお聞きしました!

Nさんのデスク。モニターアームでモニター下のスペースも活用しています。
左に置いてあるGoogle Nest Hubで音楽を聴いたり映画を観たりするのもお好きなんだとか。

よろしくお願いします!

早速ですが、Nさんがどのような環境でお仕事されているのか教えて下さい!

寝室に、デスクとバランスボールを置いて仕事をしています。

リモートワークを始めたばかりのときは、小さな台で仕事をしていたんですが、腰と背中が痛くて・・・。

新しくデスクと椅子を買ったんですけど、そのデスクも結局大きいものに買いかえて、椅子もバランスボールに変えました!

アップデートを繰り返して、今の仕事環境になったんですね!

椅子はどうしてバランスボールに変えたんでしょうか?

一時期、腰を悪くしていたんですけど、その時にバランスボールが腰に良いって聞いたんです。

ボールの上でブリッジするかんじで背中を伸ばしたりもできるし、便利ですね!

そうなんですね! 私もバランスボールに興味がでてきました!

他にデスク周りでこだわりのポイントはありますか?

モニターアームを使用するようにしています。
モニターの下にスペースができるのが便利で、私物のPCを置いたりしています。

デスク上のスペースは限られてますもんね!
一工夫するだけで、デスクを広々と使えそうです!

 

三人目は入社1ヶ月のAさんにお話をお聞きしました!

Aさんのデスク。PCとモニターの高さを合わせています。

よろしくお願いします!

早速ですが、Aさんがどのような環境でお仕事されているのか教えて下さい!

左側にモニター、右側にMacBookを置いて作業しています。

何かこだわりのポイントはありますか?

まず、PCはPCスタンドを使って、モニターと高さを合わせるようにしています。

あとは、ハブにこだわってますね!
MacBookから出るケーブルが1本になるようにしています。

Aさんの使用しているハブ。YubiKeyや有線LANなどをつなげています。

それは便利そうですね!

そうなんですよ。充電もできるし、有線LANもつなげます。

あとは、YubiKeyというものもハブに挿していて、二要素認証もできるようにしていますね!

YubiKeyは初めて知りました!
わざわざスマホを見る必要もなくて、わずらわしさが減りそうです!

このハブだけで、本当に色々なことができる。
なのにMacBookから出ているケーブルは1本で済んじゃうんです!

私も真似して買ってみようかな・・・

たしか5万くらいしますけどね!(笑)
でも、それだけの価値はありますよ。

 

最後に筆者のデスクを紹介したいと思います!

筆者のデスク。

こだわりポイントは、なんといってもオーダーメイドで作ったデスクです!

奥行きがあって広々と使えるデスクが良かったので、160cm × 80cmのサイズにしました。
また、配線孔を3つ作ることで、ケーブルをスッキリさせています。

あとは、地味に重要なのがデスクの高さです!
筆者は体型が小柄で座高も低いため、デスクの高さも一般的なものより数センチ低くしました。

 

まとめ

協力してくださったみなさん、ありがとうございました!
それぞれこだわりのポイントを持っていて、普段どのような環境で仕事をしているのかも知ることができて面白かったです!

改めてになりますが、リモートワークには、働きやすく過ごしやすい環境作りが重要となります。
リモートワークを活かして、各々の最高の環境を作ってください!

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

 

withにiOSエンジニアとして1ヶ月。これ便利!と思った3選!

はじめに

初めまして。
2022年9月よりiOSエンジニアとしてjoinしました、ashi_psnです。
私は2016年4月から新卒でエンジニアとなり、今年で7年目となります。

これまでに2016年4月から2017年8月までは、医療系スマートフォンアプリ/webアプリケーションの開発、 2017年9月から2022年9月まではグルメメディア系スマートフォンアプリの開発とモバイルアプリチームのリーダーをしてきました。

そして2022年9月、withにiOSエンジニアとして入社しました。

今回は入社してから約1ヶ月が経ち、まだまだ全体像が分かりきっていない状態ではありますが、withのiOSで用いられるツールで便利と思った点が多々あり、それを便利度、おすすめ度それぞれを5点満点で評価したものを3つご紹介いたします。

①fastlaneでの自動化

便利度 ★★★★★

おすすめ度 ★★★★★

【内容】

  • 証明書の更新(match)
  • アプリバージョン更新
  • リリースの自動化

【おすすめポイント】
証明書管理管理はmatchで行っていますが、更新と取得でそれぞれレーンが別れていて、更新処理をするとslackに他のエンジニアに証明書ダウンロードを促すチャットが流れます。
これによって証明書の更新が漏れが起きにくく、とてもいいと思いました。

また、AppStoreへのアップロードなどのリリース作業も、
プロジェクトのバージョン変更とデプロイがfastlaneで1コマンドでできます。

fastlaneも多くのiOSエンジニアに浸透したと思う一方、まだまだXcodeでバージョンやbuild番号を手動で変更してXcode上でArchiveしApp Storeへアップロードしている人も多くいるかと思います。
そんな中fastlaneのコマンドを叩くだけでバージョン設定とリリース作業ができるのは、コーディングに専念ができる時間が増え、生産性があがりとても捗りそうだなと感じました。

②テスト用バイナリ配布の自動化

便利度 ★★★★☆

おすすめ度 ★★★☆☆

【内容】

  • Pull Request上で「/deploy」とコメントを打つと、AdHocスキームでテスト用バイナリの作成とダウンロードリンクをコメントに記載してくれる

【おすすめポイント】
iOSチームではgitflowでgit運用をしており、新機能開発ではfeature/xxx のブランチで機能開発を行い、developブランチへPull Request作成してレビューを行い、developへマージ後、releaseブランチでQAをしてリリースする流れとなっています。

以前勤めていた会社では、QAのバイナリはTestFlight、もしくはエンタープライズアカウントでビルドしたものを用いていましたが、TestFlightの場合は都度ビルド番号やバージョンを更新してアップロード、エンタープライズアカウントでのビルドは証明書の付け替えが必要で、地味に手間がかかっていました。

弊社では、EMLancherというツールを用いてテスト用バイナリを配信しており、「/deploy」コマンドを叩くとCI上でAdHocビルドを行い、EMLancherへアップロードされ、ダウンロードリンクがコメントに残る仕組みがあります。 これによって、バイナリごとにどの改修が含まれているかわかり、気軽にレビュー中に動作テストも行えるため、とても便利です。

デバッグメニュー

便利度 ★★★★☆

おすすめ度 ★★☆☆☆

【内容】

  • ステージング環境ごとにビルドしなおす必要がなく向き先が変更できる。
  • ストアカウントへのログインが入力なしで行える。
  • カスタムスキームやモーダル、アラートなどの機能を単体で確認することができる。

【おすすめポイント】
サーバーは開発環境、ステージング環境、本番環境の3つがあるのは一般的かと思いますが、
多人数で開発してる場合はステージング環境も複数あることがあります。
その場合、サーバーの向き先ごとにプロジェクト内のスキームで定義していては、管理コストがあがり、向き先を変更するごとにビルドし直さなければなりません。

withではデバッグメニューからサーバーの向き先を変更できるため、ビルドも1回で済み、また設定ファイルもステージング環境用を共通で用いることができます。
そのため、QAの際にバイナリを何度も入れ直す必要がありません。

デバッグメニューの恩恵はそれだけでなく、テストアカウントへのログインがパスワードの入力など一切なくできたり、単体機能としてテストしずらいカスタムURLスキームやユニバーサルリンクも、デバッグメニューから確認することができるなど、多くのメリットがありました。

最後に

今回紹介した内容は、比較的知っているメジャーで、取り組んでいる企業や個人の方もいらっしゃるかと思います。 僕自身もfastlaneは導入経験があり、デバッグメニューは使っている企業も一部はあるというのは知っている程度でした。
ただ、その一方で、やはりあるとないとでは大きく違う、そんなことをwithに入社して1ヶ月ではありますが感じました。

今回は3つのことを紹介しましたが、他にもSwiftLintによる静的解析でコーディング規約に準拠させたり、ライブラリ更新の自動化なども取り組んでいます。

私たちiOSエンジニアは、iOSアプリの実装が主な業務となる一方、証明者管理やApp Storeへのアップロード、審査などコーディング以外のことも行わなければなりません。
そういったコーディング以外の部分をなるべく自動化してコーディング時間を作ることも、また一つ大事なことだと感じました。

DroidKaigi 2022 で気になるセッション

こんにちは。with で Android エンジニアをしている @maxfie1d です。 今年も DroidKaigi の季節がやってきました!ということで本記事では with の Android チーム的に気になるセッションを紹介したいと思います。

DroidKaigi とは

DroidKaigi はエンジニアが主役の Android カンファレンスで、2015 年から毎年開催されています。近年はオンライン開催が続いていましたが、今年はオフライン会場があります。 with からは @maxfie1d が参加します。

当日は恐らくこんな感じの名札を提げているはずです。

Android エンジニア3年目にして今年初めてプロポーザルを出しました。悔しいことに採択には至りませんでしたが、また来年チャレンジしたいと思います(過去には with のエンジニアが登壇したことがあります)。

また DroidKaigi では毎年公式アプリが公開されています。こちらは OSS になっていて、その年ごとの最新の技術が目白押しなので非常に勉強になります。私は以下 2 つの Issue でコントリビュートさせていただきました。

github.com

github.com

play.google.com

DroidKaigi は非常に実践的で内容が充実しているので、アーカイブ配信後には参加しなかったメンバーを含め Android チーム全員で視聴するようにしています(ウォッチパーティーと呼んでいます)。

気になるセッション

タイムテーブルを見て、気になるなと思ったものをまとめました。(順序は開始時刻順です。)

Jetpack Compose で Material Design 3 (Yuki Anzai)

with では 記事執筆時点で Material 2 を使用していますが、近々 Material 3 の Compose 実装が Stable になれば具体的な移行計画を検討する予定にしています。本セッションで Material 3 の知見を増やせればと思います。

「アプリのブランドカラーを保持しつつ Dynamic Color に対応する方法」が特に気になっています。

droidkaigi.jp

Prepare your project for KMM (Oussama Ben wafi)

近年身近なプロダクトでも KMM(Kotlin Multiplatform Mobile)Flutter が採用されるようになり、マルチプラットフォーム技術がすぐそこまで来ているなと感じます。候補者の方とカジュアル面談をしていも必ずといっていいほど話題に挙がります。記事執筆時点で with では iOS/Android 間で共有しているコードはありませんが、共通化できたら面白いだろうなと日頃から夢を見ています。

KMM は依然として Beta 段階ですが、マルチプラットフォーム技術として最も有望視しているので注目していきたいと思います。

droidkaigi.jp

アプリエンジニアと QA チームがデバッグ機能の改善に取り組むぞ! (Satoru Komai)

たいていのアプリにはデバッグのための機能が備わっていると思うのですが、with にも通称デバッグメニューが存在しています。機能としては、ログイン、サーバーの切り替え、各種フラグの編集等をサポートしています。

セッションの説明文で指摘されているように、デバッグ機能はエンジニアが必要だと思ったものが無秩序に追加される傾向にあるので気付いたら複雑で使いづらいものになってしまうという問題があると思います。

QA チームも巻き込んでデバッグ機能を改善した事例が聞けるということで、非常に参考になりそうだと思いました。

ちなみにこちらが with Android 版のデバッグメニューです。使いやすくて期待される機能が搭載されているかというと正直微妙なので、改善したいなと思っています。

droidkaigi.jp

プロダクトで安全に DataStore 移行する (Go Takahana)

with では最近 DataStore を使用し始めました。セッションの説明文で指摘されているように「移行しなくても不便がない」というのがあり DataStore への完全移行の予定は今のところありません。しかしながら、SharedPreferences のコードを残しておくのも将来的に負債になりそうなので理想的には早めに移行を済ませたいところです。

このセッションでは実践的な DataStore 移行のテクニックを学べればと思います。

droidkaigi.jp

All your Compose @Previews to screenshot tests without instrumentation (David Vávra)

with では Showkase を使って UI カタログを作成し、手軽に UI を確認できるようにしていますがスクリーンショットテストを含む UI テストは行っていません。

かつてスクリーンショットテストは高コストというイメージがありましたが、Compose 時代になって手が届きやすくなったのかなという印象があります。このセッションを見て with にも導入できそうか検討したいと思います。

droidkaigi.jp

はじめようビルドメトリクス (こまたつ)

with では 2022 年にマルチモジュール化を行いました。理由の 1 つとしてビルドパフォーマンスの向上があったのですが、作業を行う中で「そもそも Gradle ってどんな風にビルドしているの?」「どこがボトルネックになっているの?」という疑問が湧いてきました。

いろいろ調べていく中で Gradle のビルドの仕組みや流れを理解できただけでなく、工夫をしないと想像以上にビルドに無駄な時間がかかってしまうということが分かりました。ビルドパフォーマンスが悪いとチーム全体のパフォーマンスに大きく影響するため、地味に重要なポイントなのではないでしょうか。

ビルドパフォーマンス向上の第一歩は計測することだと思うので、このセッションは参考になりそうです。

droidkaigi.jp

おわりに

以上気になるセッションについてまとめました。

個人的な話になりますが、私は 2020 年に新卒で Android エンジニアになりました。エンジニアになったはよいものの、コロナ渦のためオフラインで技術カンファレンスが開催できない期間が続きました。しかし今回の DroidKaigi はオフライン会場があるということで参加が非常に楽しみです。本記事で取り上げなかったセッションももちろんのこと、どっぷりとAndroidの世界に浸かってきたいと思います✌ 終了後には参加レポートを書きます。

参考: with では上長の許可があれば技術カンファレンス等に自由に参加でき、参加費も補助されます。もちろん業務として扱われます。(制度は変更になることがあります)。

リンク

with の Android チームにおけるモジュール分割の取り組み

はじめに

こんにちは。with で Android エンジニアをしている石田です。Android エンジニアになって丸 2 年が経ち、時の流れはあっという間だなと思う今日このごろです。

with の Android チーム(以下 with-android)では新機能の開発に加えて日々積極的に技術的負債の解消に取り組んでいます。 2022 年度はマルチモジュール化に取り組みはじめました。おそらく 1 年間くらいかかるであろう長期の取り組みになりますが、本記事ではその途中経過を考察を交えてご紹介したいと思います。

公式のモジュール分割ガイド

先日公式にて Guide to Android app modularization というモジュール分割に関するガイドが公開されました。これによりモジュール分割も Android 開発において当たり前のことになっていくのかなと思います。 このガイドでは 推奨アーキテクチャ における推奨モジュール構成が分かりやすく詳細に説明されています。

例えばモジュール分割によってのみ達成できる利点として以下の 3 つが挙げられています。

  • Reusability (再利用性)
  • Strict visibility control (厳格な可視性の制御)
  • Customizable delivery (カスタマイズ可能なデリバリ)
    • ※with-android では現在 Dynamic Feature を使用していません

また、モジュール分割によっても達成することができる利点として以下の 5 つが挙げられています。

  • Scalability (スケーラビリティ)
  • Ownership (オーナーシップ)
  • Encapsulation (カプセル化)
  • Testability (テスタビリティ)
  • Build time (ビルド時間)

このようにモジュール分割をすることには非常に多くの利点があり、その名前以上に大きなパワーを持っていると言えると思います。ある程度の規模のコードベースの Android アプリではやらない手はないかなと思います。反対にモジュール分割における落とし穴の記載もあるので注意しておくとよさそうです。

nowinandroid はちょうどこの公式ガイド通りのモジュール構成になっているので、実際のコードで確認したい場合はこちらを参照するとよさそうです。

以下、本記事における「マルチモジュール」とはこの公式ガイドにあるようなモジュール構成を指すものとします。

with-android におけるこれまでのモジュール構成

モジュール分割に着手する前の with-android のモジュール構成を説明します。 with-android ではクリーンアーキテクチャを意識した 標準的な MVVM を採用しています。モジュール構成も MVVM をそのまま表現した形になっていて、ざっくりいうと data, domain, presentation といったモジュールが存在するだけでした(実際のモジュール数は約 20 個)。

■ domain モジュール: モデル、リポジトリ、ユースケース など
■ data モジュール: API、エンティティ、永続化 など
■ presentation: UI、ViewModel、遷移 など
※ 図の矢印は依存の方向を示しています

モジュールの数は最小限で with-android の歴史とともにそれぞれのモジュールの肥大化が問題となっており、そろそろ何とかしたいなという状況でした。

モジュール分割に踏み切った理由

モジュール分割による利点は上で紹介した公式ガイドの通りなのですが、with-android において重要なポイントは次のようなものが挙げられると考えました。

ビルド高速化

Gradle には キャッシュや Incremental build の仕組みがあるので一概には言えないですが、1 つ 1 つのモジュールサイズが大きいことはビルドパフォーマス低下を招きます。 ちょっとコードを変更しただけなのにビルドに時間がかかると待ち時間が発生するだけでなく、作業の集中力が切れてしまう原因にもなります。 マルチモジュール化によりビルドの並列性が向上し、ビルドの高速化が期待できます。

厳格な可視性の制御

Kotlin には package private がありません。クラスの可視性を狭いスコープに閉じ込めるにはモジュールを分ける必要があります。 マルチモジュール化によりモジュールの外から見える必要のないクラスを非公開にすることができます。

目的のコードを早く見つけられる

モジュール分割を細かく行っていないと Android Studio 上ですべてのモジュールを読み込む必要があります。すると目的のコードを見つけるのに時間がかかるので気持ちよく開発することができません。Project View にも大量のコードのリストが並ぶことになり、視覚的にもノイズになります。 マルチモジュール化により、作業中のモジュールが依存しているモジュールだけを読み込んで作業できるようになります。

再利用性

with-android ではまだ至っていませんが、マルチモジュール化により with の一部の機能だけ(例えばトーク機能のみ)を切り出したミニアプリを作ることができるようになります。今後のサービス成長の中でアプリの機能は増え続けることが予想されるため、素早い開発サイクルを回すためにも近々実現したいと考えています。

モジュール分割作業

ボトルネックはどこか

with-android の場合、一番のボトルネックは presentation モジュールが巨大であることです。計測したところ presentation モジュールの全体に対するコード行数の割合は 7 割であり、ビルドコスト も Build Analyzer によると 7 割を占めていました。

presentation モジュールに問題が集中していることが分かったので、presentation モジュールを共通機能(core)と個別機能(feature)で分割していくことにしました。このあたりは公式ガイドと同じ方針です。

domain や data モジュールも同様に分割していきたい気持ちはありますが、まずは presentation モジュールの分割に集中することにしました。

実際の作業

施策の実装で既存機能に改修を入れるとき、新規機能を作るとき、手が空いたときなど様々なタイミングでモジュール分割を行っていきました。with-android は 4 名のチームですが、分担するとコンフリクトが起きやすくなったりなどの影響がありそうだったので筆者がほとんどの作業を行いました。

肝心のどうやるかですが、presentation モジュール内のクラスの依存関係はこれまで特にルールが存在していなかったためある程度の複雑度があり、残念ながら自動でモジュールを分割するといったことは不可能であると考えました。よって Kotlin, XML, リソースなどを手動で移動していきエラーが出た箇所をよしなに修正するという作業を繰り返しました。

Android Studio(IntelliJ)には 依存関係分析機能 があり、あるクラスがどのクラスに依存しているか(またはどののクラスから依存されているか)を調べることができます。 特に複雑な部分ではこの機能を使いながら紐解いていきました。

Code > Analyze code > Dependencies...
Code > Analyze code > Backward dependencies...

モジュール分割を進めていくと画面遷移をどうするかという問題が発生するのですが、今のところは遷移用のインターフェースを介して遷移するというシンプルな方針を取っています。

結果

作業前後のモジュールサイズの変化を可視化してみました1。 1つ1つの円がモジュールに相当し、面積はモジュールサイズを表しています(簡単のために モジュールサイズ = Kotlin の LOC としています)。 色が黄色に近いものほど大きいことを表しています。

【Before 2022/03】

半年前はモジュールが 約 20 個で、上部にある黄色の presentation モジュールが圧倒的に大きいことが確認できます。この状態が半年間で次のように変化しました

【After 2022/08】

前後で比較すると以下のことが言えます。

  1. モジュール数が約 2 倍に増えた (約 20 個 → 約 40 個)
  2. モジュールサイズの差が小さくなり、バランスが良くなった
  3. 依然として presentation モジュールが最も大きい

いかがでしょうか。作業途中ではありますが前後で比較するとそこそこモジュール分割の度合いが改善されたのが確認できると思います。

モジュール分割作業を進めていく中で思ったこと

  • モジュール分割作業は自動化が難しく手作業になるため、根気のいる作業になります。担当者を交代しながら集中的に作業するのがよさそうだと思いました。
  • internal が付いていることで「あ、このクラスはこのモジュールだけで使うんだな」ということがひと目で分かるようになり、妙な安心感があるなと感じました。また普段から Visibility を意識する癖がついたように思いました。
  • 日々の開発の中でビルドが明らかに早くなったと感じる場面が多々あり、開発がしやすくなったなと思いました。
  • 頭で分かっていてもついつい NG な依存関係を作ってしまっている部分がありました。そこで module-graph-assert という Gradle プラグインを導入してモジュール依存関係のルールを強制するようにしました。 これは最初にやっておけばよかったと後になった後悔したことの 1 つです。
  • モジュールの数がかなり増えたので、build.gradle の記述を Composite build で共通化する必要がありそうだなと思いました。

まとめ

本記事では with-android におけるモジュール分割の取り組みについてご紹介しました。まだ道半ばではありますが、その効果にはかなり満足しています。より綺麗で快適なコードベースを目指して残りの作業も行っていきたいと思います。モジュール分割作業完了後は with の一部の機能だけを切り出したミニアプリを実現していきたいと思っています。

参考リンク


  1. 図は R の packcircles というライブラリで作成しました