クックパッド採用説明会「クックパッドはサービスの作り手を採用したいんです。」を開催しました!

こんにちは、メディアプロダクト開発部の長田(おさだ)です。

クックパッドは、エンジニア、デザイナーを絶賛大募集しています。先日「クックパッドはサービスの作り手を採用したいんです。」というイベントを開催したのでその時の様子をお伝えします。
https://cookpad.connpass.com/event/149581/

開始

まずはクックパッドの紹介から始まりました。

f:id:osadake212:20191108125332j:plain

ご存知ない方もいらっしゃるかと思うのですが、クックパッドではレシピサービス以外にも、スマートキッチンサービスの OiCyクッキング Live 配信が視聴できる cookpadLiveおいしい食べ方を学習できる たべドリ生鮮食品 EC プラットフォームの cookpad mart料理が楽しくなるマルシェアプリ komerco など、たくさんのサービスを開発しています。

また、レシピサービスは全世界に展開しており、2019年10月末時点で73カ国/地域、30言語に対応しています。全世界の月間利用者数は1億人近く、投稿されているレシピ数は590万品を突破しています。

完成されたサービスに見えるとよく言われるクックパッドですが、実はやりたいことの1%もできていないのが現状です。
「毎日の料理を楽しくする」ことで世の中をよくしていきたい我々は、レシピサービスにとどまることなく、 いくつもの新しい機能や新規事業を立ち上げ拡大している最中です。

このイベントは、そんなクックパッドで一緒にサービスを作ってくれる仲間を探すために開催されました。
発表のセクションでは、クックパッドのサービス開発の様子をクックパッドマート、 cookpadLive の開発を通して紹介しました。

発表① クックパッドマート ディレクターのいない◯◯な開発スタイル

クックパッドマートからは、長野 佳子(@naganyo)・米田 哲丈(@tyoneda)による、開発スタイルについての発表を行いました。

f:id:osadake212:20191108125342j:plain

クックパッドマートでは「なにをつくるか」をどのように決めるのか、またそれをどのように実現しているのかについて紹介しました。

「なにをつくるか」は KGI ブレークダウンで決まるのがベースになります。
さらにそれだけではなく、サービスに対する「気づき」を得る機会を増やし様々な観点からサービスを俯瞰することで、なにをつくるかが日々生まれています。
生まれてきた「なにをつくるか」は事業的な観点から絞り込まれ、開発・リリースされていきます。

また、「どうやってつくるか」についてはディレクターがいないので、全員で起案し、デザインも開発も同時に進めています。
アイデアの可視化、開発しながら機能をブラッシュアップ、リリース後にチューニングするなど、職種にとらわれない「サービスの作り手」が集まっています。

詳しくはこちらの資料をご覧ください。
https://speakerdeck.com/tyoneda/20191030-kutukupatudomato-deirekutafalseinaioonakai-fa-sutairu

発表② cookpadLive 短期間で行うサービス開発術

cookpadLive からは、若月 啓聡(@puzzeljp)・長田 卓哉(osadake212)による、こちらも開発スタイルについての発表を行いました。

f:id:osadake212:20191108125351j:plain

cookpadLive では、短期間でサービスを実現するために、デザイナー・エンジニアがどのような動きをしているのかをそれぞれの視点で工夫していることを紹介しました。

デザイナーはビジネス要件(納期)も考慮しながらエンジニアがより高速に開発ができる取り組みをしています。
取り外し可能なデザインを作成することで、サービスの価値を損なわないことを担保しながら、エンジニアの実装の都合に合わせて柔軟にデザインを変更することが可能になります。

エンジニアは、設計や実装だけではなく役割を越えて、アイデアだし・仕様・画面遷移の検討・オペレーションの検討を行い、サービス開発に積極的に関わっています。
職種にとらわれず、全員がサービスの成長に対してできることを実行しながら開発を進めています。

詳しくはこちらの資料をご覧ください。
https://speakerdeck.com/osadake212/cookpadlive-duan-qi-jian-dexing-usabisukai-fa-shu

Q&Aパネルディスカッション

このセクションでは、イベント開始前に受け付けていた質問や、発表を聞いて気になった質問に対して社員が答える形式で、サービス開発や社内の様子等についてディスカッションしていきました。

f:id:osadake212:20191107175329j:plain

ありがたいことに、タイムテーブルの40分では答えきれないくらいの質問をいただきました。
質問内容は、クックパッドマート・cookpadLive のサービスについて、クックパッド全体のサービス開発チームの雰囲気について、入社してからの働き方についてなど、サービス開発だけではなく幅広くご質問をいただきました。

個別面談・懇親会

f:id:osadake212:20191108125633j:plain

「イベントでいきなり個別面談ってなに?」となりそうですが、本イベントでは希望者の方に、エンジニア採用責任者の一人である勝間(買物事業部 副部長)、デザイナー採用責任者の一人である倉光(デザイン戦略部 部長)と個別に一対一でお話ができる場を設けました。
一人10分弱の面談枠をそれぞれ4つ用意していたのですが、いったい何人の方に個別面談していただけるのだろうか・・・と不安だったのですが、なんと全ての枠が埋まり8名の方と面談することができました。

懇親会では、弊社のサービス開発に関わっているエンジニア、デザイナー6名がそれぞれテーブルにわかれ、ざっくばらんに参加者とお話させていただきました。
私、長田のテーブルでは、クックパッドの開発基盤の話から、サービスのグロースの話、チーム・プロジェクトマネジメントの話など、本当にたくさんのことについて話すことができ、弊社の様子をより伝えることができたのではないかと感じます。

まとめ

冒頭でも触れたように、クックパッドでは一緒にサービスを作ってくれる仲間を募集しています。
現在募集中のポジションはこちらからご確認いただけます。
https://info.cookpad.com/careers/jobs/

「まずは話だけでも聞いてみようか」というのも大歓迎なので気軽にお声がけいただけると幸いです。
recruit@cookpad.com

Firebase In-App MessagingのUIをカスタマイズして運用する

Komerco事業部エンジニアの岸本(@_sgr_ksmt)です。
昨年Cloud Firestoreのrulesのテストを全てローカルエミュレータを使うように書き換えた話を書いてからだいぶ間が空いてしまいましたが投稿します。

今回はFirebase In-App Messagingを利用する際にカスタムUIを適応して運用している話をしたいと思います。

f:id:sgrksmt:20191025105713p:plain:w300

In-App Messaging

Firebase In-App Messaging(以下FIAMと呼びます)は、指定した条件で絞り込んだアクティブユーザーに対して、
メッセージやボタンのアクションを設定し、アプリ内で表示するためのFirebaseの一つの機能です。
表示形式としてはいわゆる「ポップアップ」「画面上部のバナー」といった形式で表示することが可能です。

ユーザーに出すための条件にアプリのターゲット、バージョン、オーディエンス、ユーザープロパティといった情報を活用することができるので、特定のユーザーにプロモーションを行うことが容易にできます。

また、メッセージを表示するUIはSDK側が標準で提供してくれるので、クライアント側はSDKをインストールするだけで実装完了になります。 また、FIAMの設定では文字の色、ボタンのアクションなど、ある程度カスタマイズすることも可能になっています。

f:id:sgrksmt:20191025105732p:plain:w600

標準で使う場合の難点

しかし、標準のまま活用するとUIに関して次のような問題点がでてきます。

  • 文字の大きさ、フォントを変更することができない
  • 文字や画像の並びを変更することができない
  • 一部の表示形式だとボタンの背景色を変更できない
  • ボタンの大きさを変更することができない


表示用UIを標準で用意してくれているのは大変うれしいのですが、どうしてもサービスのUIと比べると浮いてしまうことと、カスタマイズ可能な範囲が狭いというのが難点になってきます。
もしかしたら読者の中にはその標準UIが微妙でFIAMの使用を断念してしまった方も居るのではないでしょうか。 というわけで、次項からカスタムUIを適応していく方法を紹介します。 (※ちなみにKomercoはiOSアプリのみ配信している関係で以降の内容はiOSでのカスタムUIの適応の話になります。

カスタムUIを適応する

公式のドキュメントにあるこちらの内容を紐解きながら解説していきます。

Podfileの編集

Podfileを開いて、Firebase/InAppMessagingDisplayFirebase/InAppMessagingに変更してインストールし直します。

- pod 'Firebase/InAppMessagingDisplay'
+ pod `Firebase/InAppMessaging'


CustomMessageDisplayComponentクラスの作成

次に、InAppMessagingDisplayプロトコルに適合したCustomMessageDisplayComponentクラスを作成します。
displayMessage(_:displayDelegate:)メソッドを実装し、引数で渡ってくるmessageのデータを判別し「Card」「Modal」「Banner」「Image Only」それぞれ表示するUIを出し分けるようにします。 以下はCardタイプの場合に、サービス内で使用しているPopupクラスを活用して表示する例を示しています。

import Firebase
import Foundation

private enum IAMDisplay {
    case unknown
    case card(InAppMessagingCardDisplay)
    case modal(InAppMessagingModalDisplay)
    case banner(InAppMessagingBannerDisplay)
    case imageOnly(InAppMessagingImageOnlyDisplay)

    init(_ messageForDisplay: InAppMessagingDisplayMessage) {
        switch messageForDisplay.type {
        case .card:
            self = (messageForDisplay as? InAppMessagingCardDisplay).map { .card($0) } ?? .unknown
        case .modal:
            self = (messageForDisplay as? InAppMessagingModalDisplay).map { .modal($0) } ?? .unknown
        case .banner:
            self = (messageForDisplay as? InAppMessagingBannerDisplay).map { .banner($0) } ?? .unknown
        case .imageOnly:
            self = (messageForDisplay as? InAppMessagingImageOnlyDisplay).map { .imageOnly($0) } ?? .unknown
        @unknown default:
            self = .unknown
        }
    }
}

final class CustomMessageDisplayComponent: InAppMessagingDisplay {
    func displayMessage(_ messageForDisplay: InAppMessagingDisplayMessage, displayDelegate: InAppMessagingDisplayDelegate) {
        DispatchQueue.main.async {
            displayDelegate.impressionDetected?(for: messageForDisplay) // ★
            switch IAMDisplay(messageForDisplay) {
            case let .card(card):
                Popup.show(
                    title: card.title,
                    body: card.body,
                    image: URL(string: card.portraitImageData.imageURL),
                    primaryButton: card.primaryActionButton.buttonText,
                    secondaryButton: card.secondaryActionButton?.buttonText,
                    buttonActionHandler: { button in
                        switch button {
                            case .primary:
                                print(card.primaryActionURL)
                                // URLを開く処理
                            case .secondary:
                                print(card.secondaryActionURL)
                                // URLを開く処理
                        }
                    }   
                )
            case let .modal(modal):
                // Modalタイプの場合の表示実装
            }
        }
    }
}



messageForDisplay.type を見ることでどの表示形式か判定できるのでそれを活用し、更にそれぞれの表示形式で扱うクラスにダウンキャストして使用します。 各種表示形式でアクセスすることが出来る情報(プロパティは次のようになっています)

f:id:sgrksmt:20191025105720p:plain:w600


また、カスタムUIを表示する際はで示しているdisplayDelegate.impressionDetected?(for:)メソッドを呼び出す必要があります。 例で示しているPopupクラスはUI含めてご自身で実装してください。

messageDisplayComponentを指定する

CustomMessageDisplayComponentを実装できたら、In-App Messagingに適応します。 次のコードをFirebaseApp.configure()の呼び出し以降で設定します。可能であればこの呼出の直後に次のコードを書くと良いでしょう。

InAppMessaging.inAppMessaging().messageDisplayComponent = CustomMessageDisplayComponent()


これで、In-App Messagingの配信をアプリが受け取った際にカスタムUIで表示することが可能になります。

カスタムUIを使う際のルールを決めておく

Komercoでは、カスタムUI側でフォントの色などを指定して運用するようにしたため、 FIAMでのメッセージ配信の設定画面では色に関する設定はしない(無視する)ようにしています。

f:id:sgrksmt:20191025105735p:plain:w600

Before/After

ここまで実装ができると、同じ設定でも変更前後でこのようにUIが変わります。

Before After
f:id:sgrksmt:20191025105659p:plain:w300 f:id:sgrksmt:20191025105650p:plain:w300

デバッグがしやすくなるTips

ちょっとしたTipsですが、FIAMは指定したアナリティクスイベントを発火させないと表示されないですが、次のようにコードでメッセージ配信設定したアナリティクスイベントの名前を指定してあげると即座に表示させることができます。

InAppMessaging.inAppMessaging().triggerEvent("show_product_detail")



- 参考: In-App-Messagingのキャンペーンを手動で呼び出せるようになった

メッセージ表示やボタンタップ時のイベントをアナリティクスに別途送りたい

カスタムUIでメッセージ表示をしたり、ボタンを押した時に別途アナリティクスイベントを収集する場合は、messageForDisplay 変数からキャンペーン名を取得することができるため、これを活用するとどのキャンペーンでのイベント発火だったのか判断することができます。

f:id:sgrksmt:20191025105741p:plain

let campaignName = messageForDisplay.campaignInfo.campaignName // 設定したキャンペーン名が取得できる
Logger.postLog(.showInAppMessaging(campaignName: campaignName))


注意点

displayDelegate.impressionDetectedの呼び忘れに注意

displayDelegate.impressionDetected?(for:)メソッドを呼び忘れすと、SDK側でユーザーが見たかどうかの集計が行われないため、条件に設定しているアナリティクスイベントが発火するたび何度もユーザーに表示されてしまいます。

ポップアップの表示制御が必要なら別途実装する

もしカスタムUIで表示するポップアップのクラスをサービス内の別の場所で使用していたり、FIAMの制御外でも非同期通信を経て何かしら表示する可能性があったりする場合は自信で重複して表示されないように制御ロジックを実装しておきましょう。

func displayMessage(_ messageForDisplay: InAppMessagingDisplayMessage, displayDelegate: InAppMessagingDisplayDelegate) {
    DispatchQueue.main.async {
        if Popup.isAlreadyShown { return }
        // カスタムUI表示処理を続行
    }
}


カスタムUIを適応すると標準UIは使えなくなる

この方法でカスタムUIを適応した場合、標準UIを呼び出すことはできなくなります。 例えば、Card、Modalタイプであれば用意したUIを使い、Banner、Image Onlyタイプであれば標準のUIを呼び出す、といったことは不可能です。
もし標準のUIを実装したい場合は、「SDK側」のソースコードを参考に作成するか、そのタイプの使用を諦めるのも一つの手になります。

KomercoではBannerタイプ、Image Onlyタイプの配信は行わない事にしたので表示実装はしていません。

まとめ

カスタムUIを適応してあげることで、よりサービスに馴染んだ形でメッセージ配信を行うことができるのでよりプロモーションに活かせるようになると思います。
標準UIを敬遠して使ってなかった方、そこがネックでFirebaseを使っているにも関わらず自前でポップアップの配信機能を実装をしていた方、これを機にカスタムUIを適応してFIAMを使ってみてはいかがでしょうか。

クックパッドアプリ(Android)の開発効率化のためにやったこと/やっていること

モバイル基盤部のこやまカニ大好き(id:nein37)です。 モバイル基盤部では、CI環境の改善やアプリのリリースサイクル自動化といった開発・リリースフローの効率化に加え、アプリのビルド速度改善や開発のしやすさを改善する様々な取り組みを行っています。 今回はその中から、クックパッドアプリ(Android)に対して行った開発効率化の取り組みの一部を紹介したいと思います。

あわせて読みたい : Android版クックパッドアプリで採用している技術の現状確認 2018年版

日々のメンテナンス系

不要になったソースコードやリソースの削除

気がつくとどこからも参照されなくなったソースコードやリソースはどうやっても発生するので定期的に消しています。 特にモバイル基盤部のタスクと決まっているわけではないですが、単に綺麗になって嬉しいこととapkサイズが少しでも小さくなれば良いという気持ちで手の空いた時に Android Studio の Analyze ツールや konifar/gradle-unused-resources-remover-plugin で検出されたものをシュッと消しています。

Lint設定の最適化/Lint警告の除去

クックパッドアプリでは以前、朝Lintという取り組みで細かいLint指摘事項への対応を行っていました。 この取り組みは警告も減るしなんとなく成果が出た気持ちになって良いものだったのですが、最終的に修正コストが異常に高かったり本当に対応が必要かと思えるような警告が残って誰も手を付けられなくなり、やがて途絶えてしまいました。

僕もすっかり朝Lintのことを忘れていたのですが、ある時なんとしても令和の朝Lintというプルリクエストを出したいと思い手元で Lint を動かしてみるとおよそ280件の指摘事項があり、うちいくつかはクックパッドアプリでは考慮しなくても良いようなものだとわかりました。 lintOptions を見ると、これは逆に対応したほうが良いと思えるものもいくつかあります。 この lintOptions をクックパッドアプリの現状に合わせたものを令和の朝Lintとしてプルリクエストにしました。

f:id:nein37:20191021143111p:plain

このときのプルリクエストでは各指摘事項の変更に対して、なぜ enable/disable にするのか、どのように修正すれば良いのかをコメントしておいたので、今後 Lint 設定を再度見直すときにも利用できると考えています。

f:id:nein37:20191021143140p:plain

同じくモバイル基盤メンバーの吉田さんが社内ブログでも朝Lint活動について広報してくれていて、朝Lintという習慣がひっそりと復活しつつあります。

※ クックパッドアプリではプルリクエストに含まれる変更に関する Lint や ktlint の指摘事項は Danger によってプルリクエスト中に指摘され修正するようになっており、朝Lintの対象となるコードを増やさないような仕組みづくりも同時に行っています。

画像リソースのWebP化/WebPおじさん化

あるときどうしても apk サイズを小さくしたくなり、画像リソースをまとめてWebPに変換しました。 Android Developers にはWebPはサイズが小さくて最高とか画像リソースをWebPに変換するとビルドが速くなるといった夢のようなことが書いてあり、それを信じて変換しました。 Android Studio には WebP の変換ツールが組み込まれていて、アプリ内の画像を一括で WebP に変換することができます。 また、クックパッドアプリは minSdkVersion21 になっているため、ロスレスや透過といった WebP の機能をフルに活かすことができるのも利点でした。 Lossy WebP への変換では画質が劣化するために以前のバージョンと比較する必要がありますが、Lossless WebPへの変換であれば理論上画質の劣化はありません。 WebP に乗り換えるためにすべての画面のすべての画像の画質チェックをしなくても良いのは便利でした。

f:id:nein37:20191021143203p:plain

6月頃に一括でアプリ内の画像およそ1000個の画像を Lossless WebP に変換し、その結果、apk サイズを1.2MB縮小することが出来ました。 それ以降は主にプルリクエストレビュー時に WebP おじさんとして活動しています。

f:id:nein37:20191021143229p:plain

得られた知見として、 Lossless WebP への変換ではほとんどの場合画像サイズが小さくなりますが、以下の2パターンではうまく縮小できませんでした。

  1. 元画像が十分に小さい Indexed Color のみで縮小されたPNGである場合
  2. 元画像がまったく軽量化されていない巨大な画像である場合

1. はWebPに変換した場合に仕組み的に縮小される余地がないためか、ほぼ誤差範囲ですがPNGよりもWebP画像のほうが大きくなる事があります。 Android Studio の変換ツールに容量が節約できない場合は変換をスキップするという設定があるため、この場合は変換対象から外しています。

2. は Lossless WebP ではなく Lossy WebP で変換すべきものです。正直見た目では Lossless にすべきか Lossy にすべきかわからないので、現状は解像度とファイルサイズをみて判断しています。

将来的にはLossless/Lossy WebPへの変換はDangerに指摘させることができると良いなと思っています。 この記事を書いている途中でプルリクエスト内に png, jpg や大きすぎる WebP が含まれている場合は Danger に指摘させるような修正を入れたのでWebPおじさん業はなくなりました。

Danger により機械化されたWebPおじさんの様子 f:id:nein37:20191021143256p:plain

なお、ビルド速度への影響は計測できないほどわずかでした。

minSdkVersion 21 後の変更

Ripple 対応

これまで background リソースを StateListDrawable で切り替えてタッチフィードバックを実装していたような箇所を Ripple によるタッチフィードバックに置き換えていきました。 API 21から RippleDrawable も使えるようになっていて、この部分は素直に minSdkVersion21 の恩恵を受けられた部分でした。

android:elevation の指定で影をつける

当たり前なんですが古い端末での挙動とか何も考えずに elevation で影が落ちるというのが本当に楽で良いのです。

*-v21 系代替リソースの整理

Style や Theme 系リソースに *-v21 で分岐させていたリソースがあったので、minSdkVersion 21 を期に整理しました。 これまで Material Design のバックポートに関する知識がないと Theme を変更するのが難しかったのですが、minSdkVersion21 になったことで Theme や Style の編集はだいぶ簡単になりました。

ツール導入など

AndroidKTX導入

クックパッドアプリ内の Kotlin 比率が高くなって来たのでAndroidKTXを導入しました。 去年の11月時点ではクックパッドアプリのおよそ20%が Kotlin でしたが、現在ではさらに Kotlin への置き換えが進み半分ほどがKotlinで書かれている状態です。 f:id:nein37:20191021143324p:plain

Firebase Performance Monitoring

去年突然アプリのパフォーマンス監視がしたくなりFirebaes Performance Monitoringを導入しようとしました。 このときはクックパッドアプリが依存していた一部のjarと競合してうまく導入できなかったのですが、今年になり Android Gradle Plugin を更新したりFirebase Performance Plugin 自体が更新されたりした結果、いつのまにか導入できるようになっていました。 現在は社内の主要なアプリでは大体有効になっていて、特にレスポンスが遅い API の特定やアプリ起動時間の測定に使われています。

Stetho から Flipper への乗り換え

もともと Stetho を利用していたのですが、Mirrativ tech blogさんの記事を参考に Flipper を試してみたら良かったので乗り換えました。 アタッチしなくても良いのは本当に素晴らしく、アプリのデータ削除などでプロセスキルを挟んだ場合でもアプリを立ち上げれば自動でログを見られるようになるのはとても便利です。

R8導入

Android Gradle Plugin v3.4 でデフォルト有効になったR8ですが、クックパッドアプリではそれ以前からR8を利用して難読化処理を利用するとビルド時間が倍になるという問題が発覚していました。 BetaやRCでバージョンが上がるたびに試していましたがまったく改善せず、とうとう v3.4 が stable になっても解決しなかったため一時的にR8を無効化していました。 そのままでもアプリのビルドはできるのですが、標準ツールが自分のアプリで利用できないのは悲しいので、定期的に時間をとって調査していました。

やるぞ!という気持ちのissue f:id:nein37:20191021143401p:plain

あるとき annotations.jar という古代のProguard設定の仕組みがビルド時間に影響をしていることに気が付き削除することで無事に有効化することができましたが、普段から最新のビルドツールや設定を試して問題を検出することの重要性を感じました。

マルチモジュール関連

以前マルチモジュールにしていく話をしてから一年以上経ち、クックパッドアプリも25モジュール構成になりました。 最初の頃は StyleTheme などをまとめたUIモジュールやログ用の仕組みをまとめたログモジュールなど、比較的変更の少ない静的なモジュールばかり切り出していましたが、これらのモジュールは切り出した後もほとんど変更が入っておらずビルドキャッシュを最大限利用できた上、位置づけもわかりやすいので早めに切り出しておいてよかったと思います。 そこから各種機能をモジュールに切り出す作業を進めていますが、:legacy に依存しないモジュールで機能を実装できるようになるまではまだもう少し掛かりそうです。

モジュールの命名

最近 Google Developers の日本語ブログにもAndroid のモジュールのパスに関するちょっとしたヒントという記事が出ていましたが、クックパッドアプリでもAndroidビューにおけるモジュール表示がわかりにくいという問題は早い段階で発覚していました。 上記の記事で紹介されていたような projectDir による解決も検討したのですが、結局 features_ のような prefix をモジュールにつけることにしました。 クックパッドアプリでは単純な解決方法を選ぶという方針でこのようにしましたが、このあたりはチームやモジュール分割の方針によって最適解が変わると思います。

モジュール切り替えによるアプリの設定変更

以前のクックパッドアプリはビルドバリアントによって接続先とデバッグ機能の有無を切り替えていました。

  • stage flavor dimension(開発用設定の切り替え)
  • mode flavor dimension(接続先設定の切り替え)
    • internal(社内ステージングサーバ向け、Hyperionなどの開発用機能あり)
    • external(本番サーバ向け、リリース用設定)
  • buildType
    • debug(minify,Proguard なし)
    • billingBeta(決済確認用の特殊なビルド)
    • release(minify, Proguard あり、リリース用証明書)

上記設定の組み合わせによってビルド時に必要な設定を利用していましたが、マルチモジュール構成のプロジェクトではアプリがこれらのビルドバリアントを設定している場合、依存しているライブラリプロジェクトにも同様のビルドバリアントを設定する必要があります。 (ライブラリプロジェクトに同名のビルドバリアントが存在しない場合、 ./gradlew testProdInternalDebugUnitTest のようなテストコマンドでライブラリプロジェクトのテストが実行されなくなる場合があります)

モジュールが増えていくにしたがってこの設定が面倒になり、 Android Studio 上でのビルドバリアントの切り替えも大変になってきたことから、 flavor ではなく依存先モジュールの切り替えによって接続先の切り替えや開発用機能の追加を行うように切り替えました。 変更後の各モジュールの依存は以下のようになっています。(dev flavor は minSdkVersion 21 化したことにより分岐がほぼなくなったので不要になりました)

  • :app_cookpad (本番サーバ向けビルドをリリースするためのモジュール)
    • :settings_external (本番サーバの接続先情報モジュール)
  • :app_cookpad_internal 社内向けアプリをビルドするためのモジュール)
    • :settings_internal (社内サーバの接続先情報モジュール)
    • :features_debug (開発用機能モジュール)
  • :app_cookpad_billingBeta 決済確認用のアプリをビルドするためのモジュール)
    • :settings_internal (社内サーバの接続先情報モジュール)
    • :features_debug (開発用機能モジュール)
    • アプリモジュールとして定義したことにより buildType billingBeta は廃止しました

この変更により、今まで ./gradlew assembleProdExternalRelease という呪文のようだったビルドコマンドが ./gradlew :app_cookpad:assembleRelease だけで良くなります。 AndroidStudio 上でもビルド対象のモジュール(=必要なアプリの種類)とbuildType(=minify,proguard,証明書)だけ意識すればよくなり、GUIでの操作もかなり簡略化されました。 クックパッドアプリはこれまでの長期間の開発で Gradle ファイルがかなり複雑化していたのですが、ビルドバリアントの整理とモジュール分割によってそれぞれ設定を書く場所がわかりやすくなり、多くの部分を共通化してシンプルな構造になっていきました。 この方式を採用するとモジュール数はどうしても増えていくのですが、クックパッドアプリのような大きいプロジェクトでも ./gradlew testDebugUnitTest のような基本的なコマンドが何も考えなくてもちゃんと動くというのは開発のしやすさという点で非常に重要だと思っているので、今年やっておいてよかった変更の一つだと考えています。

おわりに

僕の趣味で比較的地味な変更ばかり紹介してしまいましたが、今年はこの他にも多くの(機能追加以外の)変更が行われています。 モバイル基盤部ではこれからも新機能を簡単に開発し、素早くユーザーに届けるためにモバイルアプリの開発効率化を続けていきます。

興味がある方はぜひ一度クックパッドオフィスに遊びに来てください。 https://info.cookpad.com/careers/

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/