Compositional LayoutとDiffable Data Sourceを使ってiOSアプリのつくれぽ詳細画面を実装する

クックパッドの事業開発部でiOSエンジニアをしている角田(id:muchan611)です。普段はクックパッドiOSアプリの検索に関する機能を開発しています。

クックパッドの基本的な機能のひとつである「つくれぽ」を表示する「つくれぽ詳細画面」を、UICollectionViewCompositionalLayoutUICollectionViewDiffableDataSourceを使って実装したので、その過程や実装方針についてご紹介します。

背景

つくれぽとは、クックパッドのレシピを見て料理をした人が、その料理を他の人におすすめするために投稿するもので、検索ユーザーはつくれぽ通してレシピを探せるようになっています。

事業開発部では「つくれぽからレシピを決める」体験を増やす取組みを行っていますが、各施策の方針を決定するために、多くのユーザーインタビュー(※)や数値分析を実施し判断材料を得ています。
そのインタビューの中で「レシピを決定するには材料情報が必要だが、つくれぽ詳細画面にはそれが表示されておらず、レシピ決定の障壁になっている可能性がある」という課題が明らかとなり、つくれぽ詳細画面に材料を表示する施策が決まりました。

今回の開発では、これまでの実装を拡張するのではなく、CollectionViewを用いて画面を作り替えることとなったため、その際に得た知見や実装方針について、ひとつの例としてご紹介できればと思います。

課題と実装方針

実はiOSクックパッドでは、2020年の春に大きなリニューアルを実施し、その際につくれぽ詳細画面を大きく変更しました。
ただ、この時に実装されたつくれぽ詳細画面では、コンテンツが追加されることを想定していなかったため、スクロールができない画面となっていました。変更前後のつくれぽ詳細画面は以下のような見た目で、以前はViewControllerの上に直接各パーツが配置されていました。

以前のつくれぽ詳細画面

f:id:muchan611:20201223222235p:plain:w160

新しいつくれぽ詳細画面

f:id:muchan611:20201223222420p:plain:w160 f:id:muchan611:20201223222452p:plain:w160

そして、今回材料コンテンツを実装するにあたって、以下の問題をクリアする必要がありました。

  • スクロールしないことを前提にした制約が多く、そのまま構造を変えずに実装を進めると、非常に複雑でメンテナンスしにくい状態になりかねない
  • 今後、材料以外にもレシピ決定に必要なコンテンツを追加していく可能性が高く、継続的にコンテンツを増やせるような構造にする必要がある

このような背景を踏まえて今後の継続的な開発を検討した結果、 UICollectionViewで画面を作り替えUICollectionViewCompositionalLayoutUICollectionViewDiffableDataSourceを利用する方針で開発を進めることにしました。主な理由は以下の通りです。

  • コンテンツの追加が容易に行える
    • 前述した通り、今後もレシピ決定に必要なコンテンツを追加する可能性があり、レイアウトの変更に強くシンプルで分かりやすい実装が実現できるCollectionViewが最適だった
  • UICollectionViewCompositionalLayout を利用することで、section毎のカラム数指定や各コンテンツのサイズ指定が柔軟で容易になる
    • 例えば、材料sectionは2カラム、それ以外は1カラムで表示するといった、文字数によるコンテンツの高さ計算を自前で行う必要がなく、それらの調整をAutoLayoutに任せることが可能
  • UICollectionViewDiffableDataSourceを利用することで、データへのアクセスも容易で安全になる
    • 表示データをインスタンス変数に保持して利用するケースと比較すると、UICollectionViewDiffableDataSourceを利用することでデータの保持をフレームワーク側に任せることができ実装が簡素化できる
    • 型による制約が強いため、データとUIの不整合を防止できる

実装内容

全てのコードを載せると全体が分かりにくくなってしまうため、一部割愛しながら実装内容についてご紹介します。

DataSourceの定義

まずdataSourceですが、以下のような定義になっています。

var dataSource: UICollectionViewDiffableDataSource<Section, Item>!

SectionIdentifierTypeにはSectionを、ItemIdentifierTypeにはItemというenumを指定しています。 それぞれのenumの定義は以下の通りです。(TsukurepoViewItemは、APIから取得したつくれぽ情報をViewにとって都合の良い形に変換した構造体です)

enum Section: CaseIterable {
    case media
    case margin
    case recipeTitle
    case recipeDescription
    case ingredientsHeader
    case ingredients
    case showMore
}

enum Item: Hashable {
    case media(media: TsukurepoViewItem.Media?, tsukurepo: TsukurepoViewItem.Tsukurepo?)
    case margin
    case recipeTitle(TsukurepoViewItem.RecipeOverview?)
    case recipeDescription(String)
    case ingredientsHeader
    case ingredients(TsukurepoViewItem.Ingredients)
    case showMore
}

このように分けた背景についてですが、まず、UICollectionViewCompositionalLayoutでは、section毎にレイアウトを組む仕組みになっているため、Sectionはレイアウト単位で分けることにしました。

そして、Itemはcell単位で分けており、cellに渡したいデータをenumのassociated valueで持つようにしています。 UICollectionViewDiffableDataSourceの初期化時に指定するcellProvider内で、各cellの更新処理を実装するため、その際に必要なデータへ簡単にアクセスできるようにするためです。

dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { [weak self] (collectionView: UICollectionView, indexPath: IndexPath, identifier: Item) -> UICollectionViewCell? in
    guard let self = self else { return nil }
    switch identifier {
    case let .media(media, tsukurepo):
        let cell = collectionView.dequeue(TsukurepoDetailsMediaCell.self, for: indexPath)
        cell.configure(media: media, tsukurepo: tsukurepo)
        cell.delegate = self
        return cell
        //..以下省略..
    }
}

dataSourceへsnapshotをapplyする処理は、下記のapply(tsukurepo: TsukurepoViewItem?)内で実装しており、この関数はviewDidLoad()内やつくれぽ情報の取得が完了した際に呼びだされます。

override func viewDidLoad() {
  super.viewDidLoad()
  //..途中省略..

  apply(tsukurepo: nil)

  presenter.tsukurepo
    .drive(onNext: { [weak self] tsukurepo in
        self?.apply(tsukurepo: tsukurepo)
    })
    .disposed(by: disposeBag)
}

viewDidLoad()が呼び出された時点では、まだつくれぽ情報を取得していないので、引数のtsukurepoがnilとなります。その場合は、media margin recipeTitleItemIdentifierTypeのみを追加し、それぞれのcellではempty viewを表示するように実装しています。
つくれぽ情報取得後は全てのsectionにItemIdentifierTypeを追加し、材料については存在する材料の数だけingredientsを追加します。

func apply(tsukurepo: TsukurepoViewItem?) {
    var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
    snapshot.appendSections(Section.allCases)

    snapshot.appendItems([.media(media: tsukurepo?.media, tsukurepo: tsukurepo?.tsukurepo)], toSection: .media)
    snapshot.appendItems([.margin], toSection: .margin)
    snapshot.appendItems([.recipeTitle(tsukurepo?.recipeOverview)], toSection: .recipeTitle)
    if let tsukurepo = tsukurepo {
        if let description = tsukurepo.recipeOverview.description {
            snapshot.appendItems([.recipeDescription(description)], toSection: .recipeDescription)
        }
        snapshot.appendItems([.ingredientsHeader], toSection: .ingredientsHeader)
        let ingredients: [Item] = tsukurepo.ingredients.map { .ingredients($0) }
        snapshot.appendItems(ingredients, toSection: .ingredients)
        snapshot.appendItems([.showMore], toSection: .showMore)
    }

    dataSource.apply(snapshot, animatingDifferences: false)
}

レイアウトの生成

つくれぽ詳細画面の構造を簡略化するとこのようになります。(2枚目はスクロール後です)

f:id:muchan611:20201223222700p:plain:w300 f:id:muchan611:20201223222719p:plain:w300

これを実現しているコードは下記の通りですが、section毎にコンテンツの高さを割合や絶対値、推定値で指定しています。
例えば、mediaはつくれぽ画像を含むsectionで、仕様上縦横比が3:4になるように表示したいのですが、この場合はgroupのサイズに次のような指定をします。

let groupSize = NSCollectionLayoutize(widthDimension: .fractionalWidth(1.0),
                                       heightDimension: .fractionalWidth(1.33))
let group = NSCollectionLayoutGroup.horizontal(Layoutize: groupSize, subitem: item, count: 1)

.fractionalWidth.fractionalHeight を指定することで、幅や高さに対する割合でコンテンツのサイズを決めることができるためです。また、説明文や材料などは文字数によって高さを可変にしたり、文字サイズ変更の際に適切な高さを適用したりするため、.estimatedを指定しています。そうすることで、コンテンツサイズが変更される時にシステム側で実際の値を計算し調整してくれます。また、最下部に表示する「このレシピを詳しく見る」ボタンの高さは固定にしたいため、絶対値で指定ができる.absoluteを利用しています。
これらのDimensionについては公式ドキュメントに詳細が記載されています。

let layout = UICollectionViewCompositionalLayout { [weak self] (sectionIndex: Int, _: NSCollectionLayoutEnvironment) -> NSCollectionLayoutection? in
    guard let self = self else { return nil }
    let sectionKind = self.dataSource.snapshot().sectionIdentifiers[sectionIndex]

    let itemHeight: NSCollectionLayoutDimension
    let groupHeight: NSCollectionLayoutDimension
    switch sectionKind {
    case .media:
        itemHeight = .fractionalHeight(1.0)
        groupHeight = .fractionalWidth(1.33)
    case .margin:
        itemHeight = .fractionalHeight(1.0)
        groupHeight = .fractionalHeight(0.03)
    case .recipeTitle:
        itemHeight = .fractionalHeight(1.0)
        groupHeight = .fractionalHeight(0.15)
    case .recipeDescription:
        let height = NSCollectionLayoutDimension.estimated(72)
        itemHeight = height
        groupHeight = height
    case .ingredientsHeader:
        let height = NSCollectionLayoutDimension.estimated(40)
        itemHeight = height
        groupHeight = height
    case .ingredients:
        let height = NSCollectionLayoutDimension.estimated(35)
        itemHeight = height
        groupHeight = height
    case .showMore:
        itemHeight = .fractionalHeight(1.0)
        groupHeight = .absolute(108)
    }

    let itemSize = NSCollectionLayoutize(widthDimension: .fractionalWidth(1.0),
                                          heightDimension: itemHeight)
    let item = NSCollectionLayoutItem(Layoutize: itemSize)
    let groupSize = NSCollectionLayoutize(widthDimension: .fractionalWidth(1.0),
                                           heightDimension: groupHeight)
    let group = NSCollectionLayoutGroup.horizontal(Layoutize: groupSize, subitem: item, count: sectionKind.columnCount)

    return NSCollectionLayoutection(group: group)
}

そして、材料のsectionでは1行に2つのitemを表示したいため、countを指定することでsectionによって表示するitemの数を変えています。
sectionKind.columnCountは、材料sectionの場合に2、それ以外は1を返します。

let group = NSCollectionLayoutGroup.horizontal(Layoutize: groupSize, subitem: item, count: sectionKind.columnCount)

このようにUICollectionViewCompositionalLayoutを使う事で、カラム数を変えたりコンテンツサイズを柔軟に指定したりすることができ、複雑なレイアウトもシンプルで簡単に実現することができます。

iOS12以下のサポートについて

UICollectionViewCompositionalLayoutUICollectionViewDiffableDataSourceはiOS12以下で利用できないため、iOS12以下で同じような実装を実現したい場合はIBPCollectionViewCompositionalLayoutDiffableDataSourcesなどのバックポートライブラリを使用する必要があります。

クックパッドでも、主要な画面においては、iOS12で表示できるようにこれらのバックポートライブラリを利用するケースがありました。しかし、公式の仕組みとの挙動の違いから少なからずサポートコストがかかっていたため、今回はiOS13以上の端末でのみ新しいつくれぽ詳細画面を表示しiOS12以下をサポートしない、という事業判断を行いました。
(本実装を行った2020年11月時点において、クックパッドアプリではiOS12をサポートしていましたが、現在はサポート対象をiOS13.1以上に引き上げています)

まとめ

ここまでに述べたように、UICollectionViewCompositionalLayoutを用いることでsection毎のカラム数指定や各コンテンツのサイズ指定を柔軟で容易に行えるため、レイアウトの実装がシンプルかつ比較的簡単になります。また、UICollectionViewDiffableDataSourceを利用する事で、データの保持をフレームワーク側に任せることができ実装が簡素化できるほか、データとUIの不整合の防止にも繋がるため、より安全な実装が実現できます。
そして、これらの仕組みを利用してつくれぽ詳細画面を作り替えることで、新しいコンテンツの追加が容易となり、スムーズに追加開発を進められる状況になっています。

施策の結果については、(レシピ決定のひとつの指標である、つくれぽ詳細から遷移したレシピ画面での)クリップ率上昇やつくれぽ一覧画面の3日以内再訪率が上昇したことが分かり、「つくれぽからレシピを決める」体験を増やすことができたと評価しています。

このように、クックパッドではユーザーインタビューや数値分析を通して施策を考え開発を進めており、一緒にサービス開発を盛り上げてくれるiOSエンジニアを大募集しております!!
カジュアル面談なども実施しておりますので、少しでもご興味がある方はぜひお気軽にお問い合わせください!

https://info.cookpad.com/careers/


※現在、ユーザーインタビューはオンラインで実施しています

Taking Advantage of Debugging Tools for Android App Development

Hello! I'm Joseph and I'm an Android engineer from the Mobile Infrastructure team.

In this post, I will talk about some of the tools that we used in debugging while working on the renewal project. The contents of this post were presented in the recently held Tech Kitchen #25.

Renewal Project

As mentioned in the article テストケース作成を仕様詳細化の手段とする実験 published earlier this year, the Cookpad app for iOS has undergone a lot of changes as part of the renewal project.

Last October, it was Android's turn and we published the Cookpad app for Android which underwent a similar renewal project. Since this is a fairly large project, it's quite challenging to develop and debug. However, we used some tools that helped us improve our workflow.

Flipper

f:id:epishie:20201223161236p:plain

Some time ago, Facebook published a tool called Stetho, a debug bridge tool build for Android using Chrome Developer Tools. Stetho is quite useful but the functionality is limited and the client is dependent on Chrome. In 2018, they released Flipper to replace Stetho and now works on iOS, Android and ReactNative apps and with its own stand-alone client built on Electron.

Flipper has two components, the app SDK which is added as a dependency to the target app, and the desktop client where users can interact and view the data sent from the app via the SDK. Out-of-the-box, Flipper includes some core plugins users can use right away to inspect various app components with minimal setup but developers can also extend it and add or publish their own plugins.

Layout Inspector

f:id:epishie:20201223161654p:plain

One of the core plugins is the Layout Inspector. This is very useful in debugging complex UI especially when screen content is obtained from an API or user-generated like some of the screens that we've added in the renewal project.

Using Flipper, the view tree of the screen can be checked and verified if the views are inflated correctly.

Client App
f:id:epishie:20201223161759p:plain:w480 f:id:epishie:20201223161806p:plain:w160

Selecting the view will reveal the view attributes, which can be updated while running the app without recompiling or restarting. Hovering on the view will highlight the said view within the app running on the device or on the emulator. It also shows within the app the view bounds and margins or paddings if the view has it. This is very useful not only for developers during debugging but also for designers when verifying the design specs. There's a lot of other features like view search and target mode so please check the documentation for more information.

Network Inspector

f:id:epishie:20201223162344p:plain

Network Inspector is also one of the core plugins. Using this tool, HTTP requests sent from and responses received by the app can easily be verified. Out-of-the-box, the Network Inspector plugin can be integrated with OkHttp as an Interceptor that can be added to the client. Since the inspector is added directly to the HTTP client, even HTTPS requests can be inspected without dealing with encryption and certificates. For clients other than OkHttp, integration code can be written by calling the appropriate methods of the SDK's network plugin object. Recently, they added the Mock feature to stub HTTP responses which is very helpful during debugging.

Shared Preferences Viewer

f:id:epishie:20201223163054p:plain:w320

Another core plugin is the Shared Preferences Viewer. In the Cookpad app, there's a custom component called Spotlight that we use when onboarding users to the features that were added in a release. This component consists of a custom view to highlight the new feature and a SharedPreference key-value to keep track of whether the user has been shown the onboarding or not.

f:id:epishie:20201223163245p:plain

Since onboarding is a one-off event, it requires deleting the app storage (SharedPreferences) to re-test and debug. With the Shared Preferences Viewer, instead of clearing the app storage, the actual key-values can be verified and modified while running the app to modify the behavior.

These are just 3 of the core plugins that we use during development. There are other plugins like the Databases plugin for inspecting local SQLite databases, and Images plugin for monitoring image loading using third-party libraries like Fresco.

Hyperion

Hyperion is a plug-and-play debug menu library designed to help fill in the gaps between design, development, and QA. By adding the library as a dependency to the Android project, usually for debug configuration only, the menu can be accessed while running the app by shaking the device or through the notification drawer.

f:id:epishie:20201223163307g:plain:w320

Hyperion includes a variety of core plugins each serving a different function. Like Flipper, Hyperion also supports custom plugins developers can add to support different use-cases.

Over the years, we've created our own custom Hyperion plugins to assist in debugging and verifying the app behavior.

Drawer Other Tools
f:id:epishie:20201223163435p:plain f:id:epishie:20201223163453p:plain

During the renewal project, some tools were created as needed to specifically help debug the new features that we were adding to the app.

Button Style Verification

Before starting to work on the renewal project, to be able to use the new Material Design components, we had to migrate from the AppCompat theme to the MaterialComponents theme. Since the project already had a lot of custom button styles declared used within the app, we were afraid existing UI design might break when changing the theme.

f:id:epishie:20201223163547g:plain:w320

Instead of checking each screen for each button style, we built a simple tool where designers can see a preview of all the button styles in a single screen. This is a very simple tool but it definitely cut the time that it takes between designers' feedback and bug-fixing.

Ken Burns Preview

f:id:epishie:20201223163618g:plain

In some of the the screens that we're added during the renewal project, series of images are shown in a slideshow with fade-in/fade-out transitions and custom animations which are called Ken Burns Effect.

The effect patterns are designed to be random, and depend on the number of images to be shown and whether there's a video included. Since this feature is already implemented in out Cookpad iOS app, the actual tweaking of the effect parameters were already done as discussed in detail in this post.

f:id:epishie:20201223163753g:plain:w320

However, designers still need to verify that the effects are played correctly and are the same as the iOS version. Testing on the actual screen is unreliable since the effects are dependent on randomness and the count and type of content. To help designers verify the feature quickly, we built this tool where they can check the effects, change the patterns and verify the effect with different combination of contents.

Quick Navigation

f:id:epishie:20201223163850p:plain:w320

During the renewal project, we were adding new features that has a set of list and details screens. Most of the time, these screens are implemented independently and simultaneously and sometimes by different developers. Because of the parent-child relationship of the screens, the actual navigation between the screens cannot be implemented until the set is completed. To allow such navigation during development, we built a simple navigation list screen so that it's possible to access the child screens.

Log Viewer

As with most apps these days, in the Cookpad Android app, we do record logs of user's actions which are useful in analyzing and understanding the status of our services. Since these logs are buffered before being sent to our log infrastructure, when adding logs to the app, to make sure that the implementation is correct, we had to wait for the logs to be printed in logcat and/or check the backend.

f:id:epishie:20201223163909p:plain:w320

To speed-up the development and debugging, we've added a simple Log Viewer tool. Since our logging library called Puree allows adding filters that can be applied to each log before sending, we created and added a filter where we can record the logs to a local database which the Log Viewer can query and display as a list.

Demo Apps

f:id:epishie:20201223163938p:plain:w320

The Cookpad Android project was split into feature modules for better code organization and cohesion, and to potentially improve the time it takes to build and run the app. However, even if the features are independent of each other, debugging and verification still involves the whole project and the whole app has to be built and run.

The Cookpad iOS app has the Sandbox apps which are mini-apps that contain a single feature that depends only on a subset of the app's modules. For Android we built a similar mechanism called Demo apps.

f:id:epishie:20201223164015g:plain:w320

With Demo apps, it's possible to build only the modules needed for a feature and provide a simple entry point for the screens a feature has instead of building and running the whole app. The details of how Demo apps are implemented in Android are described in this post.

Final thoughts

We all know that developing large projects like the Android renewal project is difficult. Debugging and testing of projects with large and complex feature set are even harder. However, in most situations, there are tools already available to address some of the pain points we encounter during development. In situations in which there's no tool yet, why not try creating one. If you do, you might want to share it so that everyone can use it too.

【開催レポ】Cookpad Tech Kitchen #25 日本最大レシピサービスのモバイルアプリ開発事情

こんにちは。クリエイション開発部の星川 (@star__hoshi) です。
2020年12月10日に Cookpad Tech Kitchen #25 日本最大レシピサービスのモバイルアプリ開発事情 を開催しました。今回は新型コロナウイルスの影響もありオンラインでの開催となりました。

f:id:star__hoshi:20201217101301p:plain

クックパッドには多くのレシピが投稿され、利用者数も多いサービスとなっています。その規模の大きさから、サービスの改善には事業的にも技術的にも独自の困難がつきまといます。

  • コードの品質はどう保つか
  • 日々の業務効率を高めるために使えるツールはあるか
  • ログを始めとした技術基盤をどう整備するか
  • その基盤を活かしてどのようにサービスを開発していくか

今回はモバイルアプリの領域にフォーカスし、このような課題に日々立ち向かっている吉田、ジョセフ、三木、星川の4名が、日々の業務を通して得た知見について発表しました。

発表内容

「基本のAndroid View開発ドキュメント」/ 吉田 万輝 (@_k4zy)

クックパッドではAndroid開発経験やプロジェクトへ関わる期間が様々な人々がいる状況でチーム開発を行っています。
Android開発を効率的に進めるため「誰が書いても大体同じような実装になる」ことを目指し、モバイル開発基盤が主導して整備している開発方針のドキュメントについて発表しました。

基本の Android View 実装ドキュメントの紹介 にも詳しい記載があるので、合わせてお読みください。

「Efficient app development using various debugging and verification tools(デバッグや検証ツールを活用した効率的なアプリ開発)」 / Joseph Iturralde

Androidアプリのリニューアルプロジェクトに伴い、開発効率改善のために様々なツールを利用したり、また必要に応じてツールを作成しました。
その時に役立ったツール、テクニック、デバッグ手法やビルドの高速化について紹介しました。

「モバイルアプリ行動ログ基盤を”大統一”した話」/ 三木 康暉 (@giginet)

モバイルアプリ上でユーザーの行動ログを記録する際、従来の方法では、仕様の共有や、ミスを防ぐのが難しいという問題がありました。
この発表では、ログのドキュメントから、モバイルアプリの実装を自動生成することで、安全なログ基盤を高速に構築した事例を紹介しています。

ドキュメントベースの型安全なモバイルアプリ行動ログ基盤の構築 に記事がありますので、合わせてお読みください。

「大統一ロガーを利用したサービス開発」/ 星川 健介 (@star__hoshi)

giginet の発表した「大統一ロガー」によって、ログ基盤が整えられました。
その大統一ロガーはサービス開発者にとってどういうメリットがあるのか、また大統一ロガーを利用してどのようにクックパッドのiOSアプリを改善しているか紹介しました。

Q&A

オンライン開催のため、ZoomのQ&A機能を使いたくさんの質問をいただきました。そのうちのいくつかをピックアップして紹介します。

ConstraintLayout を使うとあらゆる View に id 名をつけると思いますが、id名の命名規則はどのようにしてますか xxxxText なのか textXxxx なのかとか

実はidの命名規則は決めてないのですが圧倒的に xxxxText が多数派です。
とても良いご指摘なのでidの命名規則もドキュメントに記載して統一しようと思います。

ログ定義はプラットフォーム間で共有されていますか?

大統一ロガーに関して、ログ定義はプラットフォーム間で共有されていません。
アプリケーションごとに画面構成が違ったり、ログの要件も違う場合があるので分離しています。

これは、過去の行動ログ基盤の運用を通し、複数のプラットフォームのログを統合すると、デメリットの方が多かったという経験に寄るものです。

ログをみてから仮説を得ることと仮説を確認するためにログをとることのどちらの方が多いですか? また、気になったことをいちいちログとりしていくの大変(=面倒くさい)ではないですか?

ログを見てから仮説を得ることもありますし、仮説を確認するためにログを取ることのどちらもあり、どちらが多いというのは難しいです。
気になったことをいちいちログ取りしていくのは大変ですが、施策を実施する前に「どの指標がどれくらい伸びたら成功とする」というのを決めることが多く、ログの取得はほぼ必須となっています。

感想

オンラインの開催となり、参加者が少なくならないか、質問があまり来ないかもと不安な中でのスタートでしたが、たくさんの方に参加いただき質問もたくさんいただきました。
Android/iOS共にアプリのリニューアルを行い、そこでの知見を発表させていただきました。Markdown でログ定義を作成しそこからロガーのコードを生成すると決定したときは「マジか、yml とかで定義書いてそこからドキュメントとコード生成した方がいいんじゃないか…」と思ったんですが、実際に運用してみると Markdown は書きやすいしすぐにプレビューも出来てとても便利でうまく回っています!

最後に

クックパッドでは、モバイル基盤部とサービス開発の部署が連携して開発しています。
クックパッドのモバイルアプリ開発やサービス開発について、もっと聞いてみたい、ディスカッションしたいという方がいらっしゃいましたら、個別雑談会も実施しているので是非お申し込みください! カジュアルに情報交換しましょう!
お申し込みはこちら → https://enq.cookpad.com/meet_cookpad_engineer