「クックパッド MYキッチン」のアプリアイコンができるまで

こんにちは投稿開発部の佐野大河です。React Native 新アプリシリーズ連載4日目はReact Native のお話はしません。今日はその新アプリ「クックパッド MYキッチン」のアプリアイコンの制作過程について書こうと思います。

f:id:sn_taiga:20180418115300p:plain

連載初日の記事でもありましたように、クックパッド MYキッチン(以下、MYキッチンアプリ)は、レシピ投稿者に使いたいと思ってもらうことを目指したアプリです。ベースの機能はクックパッドとは大きく変わらない派生アプリのようなものですが、レシピ投稿者に好んで使い続けてもらえるように、全く新しいアプリとしてユーザーに届けたいという想いがありました。なので、MYキッチンアプリのアイコンを考えるにあたって、通常のクックパッドアプリのブランドイメージを引き継ぎつつも「MYキッチンアプリらしい」シンボルをいちから考えました。 今回はこのアプリアイコン完成までのプロセスを(迷走したことも含めて)紹介していきます。

🏃 まずは情報収集

まず最初にアイデアを出すための情報収集を行いました。MYキッチンアプリのサービス開発の過程で「アプリケーション定義ステートメントシート」というものを作成し、そこからMYキッチンアプリの要素を抽出しました。

アプリケーション定義ステートメントシート

ここではサービスのコアの価値やターゲット、ユースケース、利用シーンなどを定義しています。このシートの使い方については過去の記事でも紹介されているので興味のある方はご覧ください。

ここからサービス名の「キッチン」や「自分の場所」「記録、蓄積」「育てる」「整っている」といったものをキーワードとして上げました。また、サービスのキャッチコピーを「料理を楽しんでいるあなたに」とチームメンバーで定め、これも要素の一つとして取り上げました。

💭 モチーフを考える

これらの情報をもとにアプリアイコンのモチーフを考え始めました。

アイデアが浮かばない

がしかし、初めは思うようにアイデアが浮かばず、理想の形に全く辿り着けませんでした。

ラフスケッチ

ひとまずキッチン周りの道具で思い浮かんだものを一通り描き出してみたり、キーワードから連想する形を描いてそれらを組み合わせたりしながら色々模索しましたが、MYキッチンアプリのモチーフとしてしっくりくるものが全く出てきませんでした。

キーワードから発想を得ようとしても「自分の場所だから家?」「記録だからペン?メモ帳?」と直接的な発想しかできなかったり「こうしたら料理が楽しい雰囲気出そう?」「そもそも楽しいってどういう状態だ?」と具体的なイメージが定まらずにしばらく迷走しました。

f:id:sn_taiga:20180418143042p:plain

それでも現状思い付くものを具体化してみましたがとても納得できるものではありませんでした。

🔍 サービスを理解する

これ以上ペンを動かしても何も出てくる気がしなかったので、あらためてMYキッチンアプリというサービスについて理解しようと立ち戻りました。先輩デザイナーからのアドバイスも受けながら、まずはサービスを表現するためのキーワードの抽出を行いました。

キーワードを抽出する

初めはサービスのコンセプトやキャッチコピーを眺めてそれをただそのまま引用するようなことをしていましたが、視野を広げて「料理」に少しでも関係のありそうな形容詞を一旦洗い出し、その中から「MYキッチンらしい」と思えるワードを絞り込みました。

料理に関係しそうなキーワード

「MYキッチンらしいものは何か」「MYキッチンを使ったユーザーにどう思ってもらいたいのか」という考えのもと、イメージに近いものをオレンジ色に、逆にこれは違うというものを青色にしてチームメンバーに共有しました。メンバーからのフィードバックも受け、最終的にMYキッチンアプリを表すキーワードを以下のように整理しました。

f:id:sn_taiga:20180418142212p:plain

「楽しい」に「充実感」が加わり、自分の作った料理を見返して達成感を得たり自己満足したりするような、そういう楽しさが大事なんだとイメージが具体化しました。また、新しい料理を作っている人たちの「アイデア」や「創造性」「自由さ」といったものもMYキッチンアプリにとって重要な要素だと気づくことができました。

キーワードを視覚化する

整理したキーワードをもとにアイコンのムードボードを作成しました。ムードボードとは、具体的なアウトプットを出す前にデザインの雰囲気やトーンを視覚化したもので、他者との認識のずれをなくしたり、発想の元にしたり、その後の制作の手助けをしてくれます。

f:id:sn_taiga:20180418142350p:plain

ムードボードを作成することで抽象的だったキーワードのイメージが視覚化されていきます。そうすると全体を通して見えてくるものもあり、例えば「静的じゃなく動的」「動的ではあるがカオスまではいかず秩序のとれた形」「単色ではなく複数色」といったように、MYキッチンらしさを表す上でキーになりそうな要素が色々と見えてきました。(このとき、最初に考えた案がいかにMYキッチンらしさを表現できていなかったかをあらためて実感しました)

サービスの人格を作る

アプリのイメージをより固めるために「MYキッチンアプリというサービスを擬人化したらこんな人」を視覚化しました。

サービスの人格

ベースとなる人物やプロフィール写真はチームメンバーが共通で知っている人物を用いると認識のずれが少なくなって良いです。

f:id:sn_taiga:20180418142422p:plain

また、この人格を憑依させたスマホのホーム画面を作り、アイコン案を並べて見てモチーフやクオリティの良し悪しを判断するのに用いました。

✍️ 形にする

ここまで整理した要素を元にもう一度手を動かし始めました。

ラフスケッチ

初めのときに比べ表現したいキーワードのイメージが固まり、ムードボードを作成したことで形の着想もしやすくなりました。キーワードから連想できる形を描き出し、具体化して色を付けたり要素同士を組み合わせたりしながらモチーフのアイデアを出していきました。

ラフスケッチ

結果、波を描いた鍋またはボウルから具材が広がっていく形が、料理をしている人の楽しさや創造性といったものを表現できそうで良いとモチーフの方向性を定めました。また、配色はブランドカラーのオレンジをメインに、アプリの主要機能である「キッチンモード」のテーマカラーに用いている緑と、色相の近い黄色または茶色を段階的に使い、動きを作りつつ全体をまとめる方向で進めました。

f:id:sn_taiga:20180418142457p:plain

上部のパーツを抽象化させ、配色や形状のパターンを出し、その中からbowlの5番が良いとなり最後の詰めの作業に移りました。

🎨 磨き込む

アプリアイコンとしてのクオリティを上げるための磨き込みを行いました。

視認性を上げる

「ホーム画面で他のアプリと並べたときに埋もれてしまわないか」という点を意識し、アイコンの視認性や存在感を上げる修正を行いました。

f:id:sn_taiga:20180418142542p:plain

まず上部のパーツをよりシンプルにしました。パーツ数が減って動きや広がりの印象が弱まった分、葉の大きさに差を付けて奥行きを出したり、緑の明度を若干下げてくっきりさせたり、パーツの向きやバランスを調整したりしました。

f:id:sn_taiga:20180418142611p:plain

柄の形状もいくつかパターンを試し、ホーム画面で見たとき一番視認性の高かった5番を採用しました。

このように形状や色味の調整を行いましたが、まだ他のアプリアイコンに比べて印象が弱いなと思うところがありました。そこで、元々全体のまとまりを出すために配色を3色に抑え段階的に使っていましたが、オレンジや緑に対してアクセントになる色を取り入れて印象を強めることを考えました。

意味を持たせる

印象を強めるのと同時に、色や要素に意味を持たせることも合わせて行いました。

ここまで漠然とボウルや具材と置いていたけど「この人が作っているものは何だろう」という疑問を抱き、「ボウルと泡立器ならスイーツ系かな」「緑は葉を表しているけど黄色の丸は何?」「赤青系の色ならベリーと連想できそうだ」といったように発想し、モチーフと色との意味付けをしました。

そうと決まれば、赤青系の中からベストな色を選んでいきます。

f:id:sn_taiga:20180418142647p:plain

予定通りアクセントの色を加えたことで全体的に印象が強まりました。赤〜青とその周辺の色を試し、サービス人格と比較したりホーム画面に置いたりしながら評価していきました。結果、赤寄りの色は楽しさが出る反面若々しすぎて人格よりも5~10歳程下だなと思ったり、紫寄りは少しお洒落感が出てちょっと違うなと思ったりして、2番の青が良さそうとなりました。

f:id:sn_taiga:20180418142711p:plain

さらに青の中でも微調整を行い、最終的に2_Aが一番アプリのイメージに一致していてこれがベストだと決定しました。

完成

アプリアイコン

あらためてクックパッドMYキッチンのアイコンは、料理を作っている人たちの「楽しさ」や「充実感」、新しい料理を生み出す「豊富なアイデア」や「創造性」「自由さ」、またそれらが蓄積し広がっていく状態を表しています。

🍵 まとめ

今回の制作過程で強く実感したのは「形が出てこない = サービスの理解が足りていない」ということで、もちろん自身の発想力不足というのもありますが、初めは「良い形を思いつかなければ」とアイデアを捻り出す感覚だったのに対し、サービスの理解や整理を徹底した後では「自然に形ができていた」という感覚に近く、最終的に納得のできる形にもっていくことができました。 紹介したプロセスは一例で他にも良い手法が様々あると思いますが、これからサービスのアイコンやロゴを作ろうとしている方の参考に少しでもなれば幸いです。明日はディレクターの五味夏季さんから「料理する人の課題を起点に施策を作る試み」のお話です、お楽しみに!

投稿開発部では、今回作成したアプリアイコンのコンセプトを、アプリ内でも強く実感できるようなサービス開発をこれからどんどん行なっていきます。興味を持たれた方がいらっしゃれば採用ページを是非ご覧ください。軽く話を聞いてみたいという方は@sn_taigaにDMしていただいても大丈夫です😄

ReactNativeプロジェクトのAndroid環境を整備する

投稿開発部の吉田です。React Native 新アプリシリーズ連載3日目はAndroid担当の私からReactNative(以下RN)のAndroidプロジェクトを出来る限り健康な状態にするために行ったことを紹介します。

手元の環境

RNは開発が活発なため執筆時点の環境を載せておきます。バージョンが乖離している場合は動かない可能性があります。

  • react-native-cli(2.0.1)
  • npm(5.8.0)
  • node(8.9.3)
  • react-native(0.55.1)
  • Android Studio(3.1)
  • macOS High Sierra(10.13.4)

Androidの開発環境はセットアップ済みとして話を進めます。

設定ファイルの掃除

react-native initで生成されたプロジェクトは丁寧な作りになっていますが、コメントが多すぎたり、設定が古かったりするので整えていきます。 RNはAndroidプラットフォームにそこまで依存性がないので、設定ファイルを大胆に書き換えても問題なく動作します。

AndroidStudioでプロジェクトを開く

AndroidStudioを起動してOpen an existing Android Studio projectを選択します。 ディレクトリは$project_root/androidを指定します。

起動すると"Android Gradle Plugin Update Recommneded" というタイトルのダイアログが出てきます。問題ないので更新しましょう。 内容はAndroidの標準のビルドシステムであるGradleの更新とGradleでAndroidProjectをビルドするためのプラグインの更新です。

Gradle pluginの更新が終わるとgradle syncやAndroidStudioのindexingが動き始めます。それらが終わると恐らく右下に下記の警告が現れます。

Configuration 'compile' is obsolete and has been replaced with 'implementation'.
It will be removed at the end of 2018


The specified Android SDK Build Tools version (23.0.1) is ignored, as it is below the minimum supported version (27.0.3) for Android Gradle Plugin 3.1.0.
Android SDK Build Tools 27.0.3 will be used.
To suppress this warning, remove "buildToolsVersion '23.0.1'" from your build.gradle file, as each version of the Android Gradle Plugin now has a default version of the build tools.

警告は2点あります。

  • ライブラリの依存関係を表す文法が変更され、古いcompileというシンタックスは2018年末を目処に削除されること
  • Android-Gradle-Plugin(3.1.0)はAndroid SDK Build Tools(23.0.1)をサポートしていないこと

上の警告は app/build.gradle以下のこの部分の事を指しているので素直に置き換えましょう

 dependencies {
-    compile fileTree(dir: "libs", include: ["*.jar"])
-    compile "com.android.support:appcompat-v7:23.0.1"
-    compile "com.facebook.react:react-native:+"  // From node_modules
+    implementation fileTree(dir: "libs", include: ["*.jar"])
+    implementation "com.android.support:appcompat-v7:23.0.1"
+    implementation "com.facebook.react:react-native:+"  // From node_modules
 }

二つ目の警告も解決は簡単です。Android SDK Build Toolsについて詳しく触れませんが、Android-Gradle-Plugin3.0以降では指定しなければ常に最新のものが利用されるので該当箇所を削除します。

 android {
     compileSdkVersion 23
-    buildToolsVersion "23.0.1"

     defaultConfig {
         applicationId "com.sampleproject"

SDK Build Tools Release Notes | Android Studio

上記の作業後画面の右上からSync Nowすると全ての問題は解決したので警告は表示されなくなります。

compileSdkVersionとtargetSdkVersionを最新にする

Android開発にはcompileSdkVersionとtargetSdkVersionとminSDKVersionの3つのバージョン概念があります。それぞれの違いについては公式ブログに譲るとして、compileSdkVersionとtargetSdkVersionを最新にします。 (2018年4月地点では27が最新)

diff --git a/android/app/build.gradle b/android/app/build.gradle
index e37c508..e645022 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -94,12 +94,12 @@ def enableSeparateBuildPerCPUArchitecture = false
 def enableProguardInReleaseBuilds = false

 android {
-    compileSdkVersion 23
+    compileSdkVersion 27

     defaultConfig {
         applicationId "com.sampleproject"
         minSdkVersion 16
-        targetSdkVersion 22
+        targetSdkVersion 27
         versionCode 1
         versionName "1.0"
         ndk {
@@ -137,7 +137,7 @@ android {

 dependencies {
     implementation fileTree(dir: "libs", include: ["*.jar"])
-    implementation "com.android.support:appcompat-v7:23.0.1"
+    implementation "com.android.support:appcompat-v7:27.1.1" //majorVersionがcompileSDKVersionと一致する必要がある
     implementation "com.facebook.react:react-native:+"  // From node_modules
 }

diff --git a/android/build.gradle b/android/build.gradle
index 3931303..7456eb1 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -17,6 +17,7 @@ allprojects {
     repositories {
         mavenLocal()
         jcenter()
+        google() //最新のsupportライブラリを取得するためgoogle-repositoryを追加している
         maven {
             // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
             url "$rootDir/../node_modules/react-native/android"

特にtargetSdkVersionを26以上に上げる作業はリリース前に必ずやっておくことをお薦めします。 今年中にtargetSdkVersionの低いアプリのリリースや更新が制限される事が公式のアナウンスで発表されています。

targetSdkVersionを上げるとアプリの一部の振る舞いが変わります。一番わかりやすい例はRuntimePermissionの有効化です。他にも細かな振る舞いの変更があるのでこの辺りで一度アプリが正常に動くのかチェックしましょう。

見落としがちな変更にランチャー上のアプリアイコンの見た目の変化があります。(ランチャーアプリによっては想定より小さく表示される) この問題は、roundIconリソースの追加することで解決します。2018年現在ではついでにAdaptiveIcon対応もすることをお薦めします。AdaptiveIconとはランチャーアプリ側でアイコンの外形を決めることの出来る機能です。どちらにせよ対応は難しくないのでデザイナーと相談してandroid:roundIconを設定しましょう。

不要なパーミッションや宣言を取り除く

ReactNativeでAndroidアプリを作ると不要なパーミッションがデフォルトでついているので適切に削除します。

DevSettingsActivityの宣言をリリース版から削除する

DevSettingsActivityは名前の通りデバック用途のActivityです。リリースビルドには必要ないのでapp/src/debug/AndroidManifest.xmlにデバッグ用のマニフェストを作り移動させます。

--- /dev/null
+++ b/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <application>
+        <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
+    </application>
+</manifest>
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index c765926..d4b02f4 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -28,7 +28,6 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
-        <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
     </application>

 </manifest>

SYSTEM_ALERT_WINDOWをリリースビルドから削除する

SYSTEM_ALERT_WINDOWは他アプリの上への描画を許可する強めのパーミッションです。こちらもデバックビルドで利用されるものですがリリースビルドにも紛れ込むので取り除く設定を追加します。

--- /dev/null
+++ b/android/app/src/release/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+        >
+    <uses-permission
+            android:name="android.permission.SYSTEM_ALERT_WINDOW"
+            tools:node="remove"
+            />
+</manifest>

READ_PHONE_STATEを削除する

ReactNative for Androidはandroid-jscというライブラリに間接的に依存しています。 このライブラリのtargetSDKlevelが4と異常に低い影響でREAD_PHONE_STATEのパーミッションが勝手に追加されます。多くの場合不要なパーミッションだと思うので削除しておきます。

--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -1,8 +1,11 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.sampleproject">
+          xmlns:tools="http://schemas.android.com/tools"
+          package="com.sampleproject"
+        >

     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove"/>

     <application
       android:name=".MainApplication"

残りは好みですが不要な設定やコメントやファイルを削除します。

  • 不要なコメントの削除
  • keyStore以下の削除
  • libs以下のjarの参照削除
  • mavenLocalを参照レポジトリから削除
  • BUCKファイルの削除

Clean up project · kazy1991/techlife-rn-android-sample@8366400 · GitHub

apkサイズの最適化

必ずしもやる必要はないですが、ReactNative製のアプリはデフォルトの設定だと不必要にバイナリサイズが大きくなってしまうのでダイエットする方法を2つ紹介します。 私達の開発しているMYキッチンアプリでは下記の二つを行い10MB程度あったapkを5MBまで減らすことが出来ました。

Multiple APKs化

Androidは多種多様な環境の端末で動く可能性があるため、必要になりそうなものは全部詰め込んでいます。Multiple APKsとは環境毎にバイナリを分けることでサイズを削減する方法です。 ReactNativeの場合Native Developer Kit(NDK)の部分が大きいためCPUアーキテクチャ(ABI)毎にバイナリを分けてあげるとapkのサイズがかなり小さくすることが出来ます。 対応が難しそうですが、実はプロジェクトの雛形の中にdef enableSeparateBuildPerCPUArchitecture = false というフラグが用意されているためtrueに切り替えるだけでMultiple APKsは完了します。

ちょっとした補足ですが、apkを分割する場合PlayConsoleの都合上versionCodeはそれぞれ異なる必要があります。テンプレートではaapt(Android Asset Packaging Tool)コマンドを使って確認しやすいように、既存のバージョンコードに1MB(1048576)を足し合わせた値を採用しています。これは個人の込みのですが、私たちはversionCodeをログ分析などでも利用する都合上パッと見のわかりやすさを重視して既存のversionCode+末尾1桁をABIの識別という形に変更しています。

diff --git a/android/app/build.gradle b/android/app/build.gradle
index f491bca..d8dc9bc 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -69,11 +69,11 @@ android {

     applicationVariants.all { variant ->
         variant.outputs.each { output ->
-            def versionCodes = ["armeabi-v7a":1, "x86":2]
+            def versionCodes = ["armeabi-v7a": 1, "x86": 2]
             def abi = output.getFilter(OutputFile.ABI)
             if (abi != null) {
-                output.versionCodeOverride =
-                        versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
+                // 例) version1.1.1のx86のversionCode: 101012
+                output.versionCodeOverride = defaultConfig.versionCode + versionCodes.get(abi)
             }
         }
     }

https://developer.android.com/studio/build/configure-apk-splits.html

Proguardの有効化

Proguardとはプロジェクトを静的解析して参照のないコードを削除したり、難読化を行うツールです。 これも詳細は下記のリンクに譲りますが、こちらもdef enableProguardInReleaseBuilds = falseというフラグが用意されているためtrueに切り替えるだけで有効になります。 ネイティブモジュールを多用しない限り嵌まらないと思いますが、正しい設定がされていないままProguardを有効にするとビルドが通らなくなったり、実行時にアプリが意図せずクラッシュすることもあります。 有効に切り替える際には動作確認することをお薦めします。また可能ならProguardの振る舞いについて正しく理解できていると安心できます。

https://developer.android.com/studio/build/shrink-code.html

Stetho(デバッグツール)の導入

StethoはFacebook社が開発しているAndroid向けデバッグツールです。おおまかに言うとAndroid開発でもChromeのdevToolが使えるようになります。 特にNetwork Inspectionはとても便利なので導入しておくことをお薦めします。

f:id:kazy1991:20180409202337p:plain

一般的な導入方法は公式のドキュメントに譲りますが、一点だけ注意点があります。ReactNative:0.45~0.54を利用されている場合公式ドキュメント通りに導入しても動作しません。ReactNativeのバージョンを上げるか下記のリンクのように強引に差し込む必要があります。

Fix NetworkingModule losing Cookies when multiple CatalystInstances e… · facebook/react-native@0a71f48 · GitHub

バージョニングをsemverぽく変更する

ReactNativeを使った開発ではCodePushを利用することも多いと思います。CodePushと相性を良くするためにAndroid側のバージョニングもsemverっぽく管理すると便利です。 AndroidのバージョニングにはversionCodeversionNameがありますがMYキッチンアプリでは下記のように管理しています。

--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -7,6 +7,7 @@ project.ext.react = [
 ]

 apply from: "../../node_modules/react-native/react.gradle"
+apply from: "${project.rootDir}/gradle/appversion/appversion.gradle"

 def enableSeparateBuildPerCPUArchitecture = false
 def enableProguardInReleaseBuilds = false
@@ -18,8 +19,8 @@ android {
         applicationId "com.sampleproject"
         minSdkVersion 16
         targetSdkVersion 27
-        versionCode 1
-        versionName "1.0"
+        versionCode project.ext.getVersionCode()
+        versionName project.ext.getVersionName()
         ndk {
             abiFilters "armeabi-v7a", "x86"
         }
diff --git a/android/gradle/appversion/appversion.gradle b/android/gradle/appversion/appversion.gradle
new file mode 100644
index 0000000..e9bdb5c
--- /dev/null
+++ b/android/gradle/appversion/appversion.gradle
@@ -0,0 +1,49 @@
+class AppVersion {
+
+    private int major
+
+    private int minor
+
+    private int patch
+
+    AppVersion(int major, int minor, int patch) {
+        throwExceptionIfVersionIsNotValid(minor)
+        throwExceptionIfVersionIsNotValid(patch)
+
+        this.major = major
+        this.minor = minor
+        this.patch = patch
+    }
+
+    private static void throwExceptionIfVersionIsNotValid(int version) {
+        if (version >= 100) {
+            throw new IllegalArgumentException("Can't use version number more than three digit")
+        }
+    }
+
+    int getCode() {
+        return major * 100_00_0 + minor * 100_0 + patch * 10
+    }
+
+    String getName() {
+        return "${major}.${minor}.${patch}"
+    }
+}
+
+ext {
+    def vProperties = new Properties()
+    vProperties.load(rootProject.file('version.properties').newDataInputStream())
+    def versionMajor = vProperties.getProperty("version.major").toInteger()
+    def versionMinor = vProperties.getProperty("version.minor").toInteger()
+    def versionPatch = vProperties.getProperty("version.patch").toInteger()
+    def appVersion = new AppVersion(versionMajor, versionMinor, versionPatch)
+
+    getVersionCode = {
+        return appVersion.getCode()
+    }
+
+    getVersionName = {
+        return appVersion.getName()
+    }
+}
+
diff --git a/android/version.properties b/android/version.properties
new file mode 100644
index 0000000..9f61962
--- /dev/null
+++ b/android/version.properties
@@ -0,0 +1,3 @@
+version.major=1
+version.minor=0
+version.patch=0

ReactNativeに限らないTips

紹介した内容以外にもReactNativeプロジェクトに限らない変更をいくつか入れているので簡単に紹介します。

おわり

ReactNaiveを使ったアプリ開発は評判通り高速に開発が可能でとても気に入っています。AndroidエンジニアはRNプロジェクトであまり活躍できない印象がありましたが、より安全に(例えば秘匿値の管理)より快適な体験(例えばSmartLock for passwordを使って自動ログインを実現するなど)を提供するために活躍する場面はたくさんあります。

もし私達の取り組みに少しでも興味を持って頂けたらぜひお声がけください。私個人へのDMでも大丈夫です! 😀

明日は@sn_taigaさんから 「クックパッド MYキッチン」のアプリアイコンができるまでのお話です。お楽しみに!

今回作成したプロジェクトのレポジトリはこちらです。

GitHub - kazy1991/techlife-rn-android-sample

React Native アプリの開発基盤構築

こんにちは、投稿開発部の @morishin127 です。React Native 新アプリシリーズ連載2日目ということで、この記事では React Native アプリの開発基盤の構築について書こうと思います。「クックパッド MYキッチン」というアプリは React Native 製で、iOS/Android 両プラットフォームでリリースされています。元々は一人の手で JavaScript (ES2017+) によって書かれていたアプリケーションでしたが、リリースまでの間に開発メンバーも増え、TypeScript の導入や CI の整備、また高速な検証のためにログ収集の仕組み作りや CodePush の導入などを行いました。それぞれ具体的にどのようなことをしたかを説明します。

セットアップスクリプト

npm-scripts を用いて npm run ios:setup / npm run android:setup でそれぞれのプラットフォームでアプリケーションをビルドするための依存関係をインストールできるようにしています。npm-scripts は package.json に定義していて、それぞれの定義は次のようになっています。

{
    "scripts": {
        "ios:setup": "cd ios && bundle install && bundle exec fastlane setup",
        "android:setup": "cd android && bundle install && bundle exec fastlane setup",
        ⋮
    },
    ⋮
}

ネイティブアプリのセットアップやバイナリ生成の処理には Fastlane を用いており、ここで行うセットアップの処理も ios/fastlane/Fastfile / android/fastlane/Fastfile に次のように定義しています。Fastlane は Ruby 製のタスクランナーで、ビルドやストアへのサブミット、スクリーンショットの撮影など様々なタスクを定義して自動化するためのツールです。

ios/fastlane/Fastfile

react_native_root = File.absolute_path('../../')

desc "Install dependencies"
lane :setup do
  Dir.chdir react_native_root do
    sh "yarn install && yarn run build"
  end
  cocoapods(use_bundle_exec: true, try_repo_update_on_error: true)
end

android/fastlane/Fastfile

react_native_root = File.absolute_path('../../')

desc "Install dependencies"
lane :setup do
  Dir.chdir react_native_root do
    sh "yarn install && yarn run build"
  end
end

また次のような npm-scripts でシミュレータでの実行スクリプトを定義しておくと、開発者はリポジトリをクローンしてから yarn run ios:setupyarn run startyarn run ios:run:debug を実行するだけで iOS アプリをシミュレータ上で実行することができて便利です。(Android も同様)

{
    "scripts": {
        "ios:run:debug": "node node_modules/react-native/local-cli/cli.js run-ios --simulator 'iPhone SE'",
        "android:run:debug": "cd android && ./gradlew installStagingDebug",
        ⋮
    },
    ⋮
}

TypeScript 導入

開発当初は JavaScript のみでしたが途中で TypeScript を導入しました。元々の JavaScript コードと混在することになるため tsconfig.json では "allowJs": true を指定しています。React Native 周りのライブラリは型定義が充実していたため、TypeScript の恩恵を受けながら開発することができました。既存の React Native プロジェクトに TypeScript を導入した手順は morishin/ReactNativePractice の README.md にまとめたのでそちらをご参照ください。基本的には公式の Microsoft/TypeScript-React-Native-Starter に倣った手順を踏んでいます。

github.com

参考までに、「クックパッド MYキッチン」アプリの tsconfig.json は執筆時点ではこのようになっています。ソースコードは src ディレクトリに、tsc によりトランスパイルされたコードが lib ディレクトリに配置され、アプリは lib 以下のソースを読み込みます。

{
  "compilerOptions": {
    "target": "es2017",
    "module": "es2015",
    "allowJs": true,
    "jsx": "react-native",
    "sourceMap": true,
    "outDir": "./lib",
    "strict": true,
    "skipLibCheck": true,
    "moduleResolution": "node"
  },
  "include": ["./src/"]
}

tsc によるトランスパイルを自動化するために npm-scripts に build:watch を定義し、開発中はこれを実行した状態でソースコードを編集しています。

{
    "scripts": {
        "build": "tsc",
        "build:watch": "tsc --watch",
        ⋮
    },
    ⋮
}

フォーマッタ導入

複数人で開発する際にはフォーマッタがあると便利なので、Prettier を利用しています。フォーマットルールはほぼデフォルトのままです。エディタのプラグイン等で保存時に自動フォーマットがかかるようにしておくと便利かもしれません。

CodePush 導入

「クックパッド MYキッチン」ではサービスの高速な検証のために、バンドルの配信に Microsoft 製の CodePush という仕組みを利用しています。CodePush を利用すると React Native アプリの JS バンドルのみをユーザーの端末に配信することができ、App Store / Google Play Store でアップデートを配信することなくアプリを更新することができます。この特徴はとにかく高速に仮説を検証したいサービス開発者にとって魅力的で、React Native を採用する大きな理由のひとつになると思っています。

導入手順

CodePush の導入手順については公式の通りなので割愛します。

運用

CodePush は一つのプロジェクトに対して複数のデプロイ環境を作ることができ、「クックパッド MYキッチン」では Production, Production-test, Staging の3つの環境を用意しています。Production は App Store / Google Play Store で配信されている本番のアプリが JS バンドルを取得する環境、Production-test は社内のみで配信しているアプリが参照している環境、Staging はチーム内でデザインや動作を確認をするためのアプリが参照している環境です。Production-test アプリと Staging アプリの違いとして、前者は API サーバーや DB も本番環境を参照しているに対し、後者はバックエンドが本番環境とは切り離されているため、コンテンツの投稿・公開といった動作テストを行うことができるアプリになっています。それぞれの社内配信の方法とデプロイのタイミングについては CI の項で説明します。

画像の Beta 帯アイコンのものが Production-test 環境のアプリ、Staging 帯アイコンのものが Staging 環境のアプリ、無印が Production 環境のストア版アプリです。

f:id:morishin127:20180416150414p:plain

ちなみにこのアイコンの帯は fastlane-plugin-badge を用いて社内配信の CI ジョブ内で付加しています。

github.com

iOS/Android アプリから CodePush のデプロイ環境を読み分ける

Production, Production-test, Staging アプリで参照する CodePush の環境を分けていると述べましたが、iOS アプリでは Build Configuration 毎に CodePush の deployment key を切り替えることで、またAndroid アプリでは Build Type / Product Flavor 毎に deployment key を切り替えることでデプロイ環境を読み分けています。React Native アプリの Xcode プロジェクトに Build Configuration を追加するとビルドが通らなくなって苦戦したのでその解決の記録を貼っておきます。同じ問題に遭われた方の参考になれば幸いです。

qiita.com

CI 環境構築

CI マシンではプルリクエストを出したときに実行されるジョブと、プルリクエストを master にマージしたときに実行するジョブがあります。前者のジョブはアプリケーションのテストコードを実行しています。後者の master にマージしたときに実行されるジョブでは iOS/Android アプリのバイナリ生成と社内配信、CodePush の Production-test 環境への JS バンドルのデプロイを行っています。CodePush の Staging 環境へのデプロイは開発者が手元でビルドしたバンドルを手動でデプロイします。そうすることで master にマージする前のプルリクエスト段階のコードもチームメンバーの Staging アプリに配信することができ、デザイナとのコミュニケーションに有用です。実際にユーザーさんが触れる CodePush の Production 環境へのデプロイは Production-test 環境にデプロイされたバンドルを Production 環境へコピーする(promote)という形で行われます。promote の実行はチャットボットを介して Rundeck 上のデプロイジョブを実行することで行っています。アプリバイナリのデプロイと CodePush のデプロイの構造をそれぞれ図1, 図2にしました。

▼図1: アプリバイナリのデプロイ

f:id:morishin127:20180416154243p:plain

▼図2: CodePush のデプロイ

f:id:morishin127:20180416154227p:plain

ログ収集

クックパッドには複数のモバイルアプリで共通して利用しているログ収集基盤があり、「クックパッド MYキッチン」のユーザーのイベントログ等の情報もそこへ送っています。ログ収集基盤というのはクックパッドのデータ活用基盤 - クックパッド開発者ブログで触れられている基盤のことで、モバイルアプリは Logend と呼ばれる社内のログ送信用エンドポイントへログを送り、Logend は fluentd を介して Amazon S3 にログを蓄積しています。S3 に蓄積されたログは社内のデータウェアハウスにロードされ、開発者はそこで分析を行います。データ活用の基盤に関して詳しくは上述の記事をご覧ください。

アプリからログを送信するに当たってバッファリングやリトライの機構が必要になりますが、これまでのクックパッドのモバイルアプリでは Puree というライブラリがその機構を担っていました。Puree には iOS/Android 両方のライブラリがありましたが、React Native から利用できる JavaScript 版は存在しなかったため、「クックパッド MYキッチン」の開発に際して作られました。

github.com

Puree に関して詳しくは過去の記事をご覧ください。

おわりに

いかがでしたでしょうか。これから React Native でやっていこうとしているやっていき手の皆さんの参考になれば幸いです。明日は@101kazさんから React Native プロジェクトの Android 環境整備のお話です、お楽しみに!

クックパッドでは毎日の料理を楽しみにするために、より良い技術を選択し、より速くユーザーさんに価値を届けられるサービス開発エンジニアを募集しています。興味を持っていただけましたら是非気軽にご連絡ください。話をしてみたいけど応募はちょっとという方は@morishin127にDMしていただいても大丈夫です🙆

👋