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していただいても大丈夫です🙆

👋

React Nativeで作った新アプリについて(5日間連載)

こんにちは投稿開発部の丸山@h13i32maruです。

今日から5日間、本ブログに投稿開発部メンバーで連載記事を書かせていただきます!

いきなり「投稿開発部で連載記事」と言われても何のことかわからないと思うので、まず投稿開発部について簡単に紹介させてもらいます。

投稿開発部は「クックパッドに投稿されるコンテンツ全般」について責任をもっている部署なのですが、中でもレシピ事業の根幹であるレシピ投稿者向けのサービス改善に力を入れています。レシピ投稿者向けのサービス改善は「どうすれば継続的に投稿したくなるのか?」「どうすれば投稿をはじめてみたくなるのか?」の2点に答えを出すことを目標に日々サービス開発に励んでいます。

そこで、本連載では投稿開発部が今年メインで取り組んでいる「クックパッド MYキッチン」という新しいアプリについて5人のメンバーで紹介させていただきます。

1日目(vol1)では「クックパッド MYキッチン」ができるまでの話をちょっとしたストーリー仕立ての文章で紹介させていただきます。普段の記事と比べると技術的なトピックは少なめなので、肩の力を抜いて気軽にお読みください。

そして、2日目以降は以下のような内容を予定しております。

「クックパッド MYキッチン」とは

今年、投稿開発部では「クックパッド MYキッチン」というアプリ(以下MYキッチンアプリ)の開発に注力しています。このアプリはレシピ投稿者が使いたいと思えるアプリを目指して、これまでのクックパッドアプリ上での体験をリデザインして作られているものです。

f:id:h13i32maru:20180413135704p:plain:w100 f:id:h13i32maru:20180413135755p:plain App Store / Google Play

ではなぜ既存のアプリ上でレシピ投稿者向けの体験をリデザインしなかったかというと、「開発・検証のスピードを上げるため」というのが大きな理由です。そのために「関わる人を少なくして、意思決定を速く」と「機能の制約を受け入れて、実装を速く」ということを行っています。

特に後者の「機能の制約を受け入れて、実装を速く」について、MYキッチンアプリではReact Nativeを採用してフルスクラッチで作られています。また、CodePushについても試し始めています。

ではここから、いかにしてMYキッチンアプリが出来上がっていったのかを紹介していきます。

Prototype Labs(2017年春)

時は遡り2017年春、当時同じチームだったiOSエンジニアがReact Nativeをアプリのプロトタイピングに使えないか調査していました。当時、彼が書いた社内ブログにはこのように書いてありました。

年末年始でReactNativeの調査をしていました。

目的はReactとcssの知見でネイティブのアプリが作れれば、アプリのプロトタイプできる人口を増やせるのでは?というところ。

xxx(とあるプロジェクト)の初期でいくつかの機能を試していたときに、「アプリに組み込んで手触りを試したい」という欲求があったものの ネイティブがかける人は少ないし、ネイティブでレイアウトを変えるトライアンドエラーはどうしてもコストが高いので 何か別の手段で試せた方が良いのでは・・?と考えたのがきっかけです。

この彼の取り組みを横目でみながら、「React Nativeというものがあって」「アプリのプロトタイピングに使えるかもしれない」という情報を得た僕は、自分でもちょっと試しに触ってみることにしました。

当時、どういうふうに試し始めたのかははっきりとは覚えていないのですが、「アイテムのリスト画面」と「アイテムの詳細画面」という基本の画面を作ったと思います。そして色々触ってみた結果「開発スピードをあげるために、完成度・機能・UI・パフォーマンスなどの制約を受け入れることができる」というものに向いていることがわかりました。そう、まさにプロトタイピングに向いていると思ったのです。*1

さらに、React Nativeを使ったプロトタイピングなら、これまで静的なプロトタイピング(ペーパーモック、InVisionなど)では諦めるしかなかった点もカバーできると思いました。

  • 日常生活で使うことができるプロトタイプ
  • 実際のデータを使ったプロトタイプ
  • データを書き込むことができるプロトタイプ

というわけで、僕の中の「React Nativeでプロトタイピング環境を作りたい」という欲求がむくむくと湧き上がっていきました。なので鉄は熱いうちに打ての精神で、React Nativeを使った社内用のプロトタイピング環境「Prototype Labs」を作りました*2

Prototype Labsの中身はというと、React Nativeを社内のプロトタイピングに特化させるために、薄いラッパーと幾つかの便利機能を追加したものです。具体的には、ファイルの配置ルール、デプロイの仕組み、ドキュメントの構築、認証周りのデフォルト実装、サーバサイドのAPIを簡単に呼び出せる仕組み、よく使うカラー・レイアウトの提供、etcという感じです。

Prototype Labsとは APIリファレンス
f:id:h13i32maru:20180413135849p:plain f:id:h13i32maru:20180413135902p:plain

ドキュメントはESDocというJavaScript向けのドキュメンテーションツールで作りました

特にドキュメント周りは力を入れて整備しました。というのも、社内のデザイナー(HTML, CSS, JSに多少触れたことがある人)にも使ってもらえるようにというのを目標の1つにしていたからです。実際、デザイナーとペアプロ的にPrototype Labsを触ってもらい、簡単な画面を作ってもらったりもしました。

そして、Prototype Labsを使って「料理まとめ(自分が投稿したレシピを自由にまとめられるもの)」という機能のプロトタイピングをデザイナーと一緒に行いました。結果、実際に日常使いをしながら議論をすることができ、主要な要件を決めるのに非常に役立ちました。料理まとめはその後、iOS版のクックパッドアプリに実装され、現在、本番環境で元気に動いています。この時一緒にプロトタイピングをしたデザイナーが当時の様子を「React Nativeで作る 「触れるプロトタイプ」の活用」というタイトルで発表しているので興味がある方は見てみてください。

裏クックパッドアプリ(2017冬)

Prototype Labsを作った後、しばらく業務ではReact Nativeを触ることはありませんでした。

一方で、プライベートでは自分の料理レシピをクックパッドに投稿しはじめました。これまでもレシピはGoogle Docsやブログなどに書き散らかしていたのですが、それらをせっかくなのでクックパッドに集約しようと思い、どんどんレシピを投稿していきました。そうするとレシピ投稿者の視点でクックパッドアプリを見るようになり、「今までレシピ投稿者向けの開発はしたことがなかったけど、来年(2018年)はレシピ投稿者向けの開発をしたいな」と思うようになりました。

で、それなら「レシピ投稿者(自分)が使いたくなるクックパッドアプリ」を作ってしまえば良いんだと思い立ちました。またしても鉄は熱いうちに打ての精神で、React Nativeを使ってオリジナルのクックパッドアプリをまるっと作り変えてしまおうと開発にとりかかりました。これが後にMYキッチンアプリの土台となるもので、社内の一部からは「裏クックパッドアプリ」「RNクックパッドアプリ」などと呼ばれることになります。

上述したとおり、裏クックパッドアプリはレシピ投稿者が使いたくなるというのを目指していたので、コンセプトや体験はオリジナルのクックパッドアプリとは大きく異なります。具体的には・・・という話をしたいのですが、ここに書くのは長くなりそうなのと企業秘密というわけで詳細は伏せておきます。この話を聞いてみたい!という方がいらっしゃれば、TwitterのDMなどから是非とも僕までコンタクトしていただければと思います。

モード切替 キッチンモード さがすモード
f:id:h13i32maru:20180413140026j:plain f:id:h13i32maru:20180413140034j:plain f:id:h13i32maru:20180413140040j:plain

特徴は左下のクックパッドアイコン/ユーザアイコンからモードを切り替えるという点です

その他に気をつけたこととしては、オリジナルのクックパッドアプリにある機能はほぼ全て使えるようにするという点です。何故かと言うと、僕は普段使いのアプリをオリジナルから裏クックパッドアプリに完全に移行したいという考えがあったからです。そうしないと、結局オリジナルのアプリを使ってしまい、裏クックパッドアプリが中途半端なものになりうまく改善できなくなってしまうと危惧したからです。

そんなこんなで、コンセプトや体験の見直しをして、それを実現させる機能を実装し、さらに既存の主要な機能の実装を完了させ、僕は裏クックパッドアプリに完全に移行することができました。開発に取り組み始めてから2ヶ月ほどかかりましたが、実際に使った時間は10日間ほどでした。しかもこの期間でAndroidとiOSの両方を作ることができたのもReact Nativeの強みだと思います。裏クックパッドアプリを社内にリリースした時のブログに同僚が以下のようにコメントしてくれました。

10日間でここまで作り上げられるのはプロトタイピングにすごいインパクトがあることだと思いました。

この速さなら自分が鍵だと思ってるコンセプトを形にして提案することで、細かい調整(人的リソース、仕様共に)に時間を取られずに本質的な議論を始めやすくなるように思います。

プロダクト開発って結構一部を変えようと思っても全体を整えていかないといけない(けど時間がないからスコープを絞って細部の変更に留まってしまう)ということがありがちだと思うのでアプリ全体を素早く作り変えて試せるのは大きな価値だと思います。

クックパッド MYキッチン(2018年春)

そして、自分で毎日裏クックパッドアプリを使ってみて、この新しいアプリに未来を感じました。なので、2018年は裏クックパッドアプリを使ってレシピ投稿者向けの改善に取り組んでいくことを決めました。

そこから裏クックパッドを「クックパッド MYキッチン」に改名し、デプロイの仕組みやコードの整理、足りていない機能の追加やデザインの修正、アイコンの作成などを経て、2018年3月にAndroid/iOSともにプロダクションにリリースすることができました。

というわけで、続く3日間ではプロダクションリリースするために取り組んだ技術的な話、アプリアイコンの話、そして最終日はユーザの課題と解決策をどのように探っているかの話を各メンバーが紹介してくれます。お楽しみに!(ちなみに明日からの記事は今回のようなストーリー仕立てではなく、いつもの雰囲気に戻ると思うのでご安心ください)

自己紹介

最後になりましたが、簡単に自己紹介をさせていただきます。

僕は2014年にクックパッドに入社しWebやAndroid周りの機能実装を担当していました。その後に幾つかの機能のPMを担当して、今年から投稿開発部のマネージャー(部長)という役割を担っています。

プライベートではESDoc(JavaScriptのドキュメンテーションジェネレーター)やJasper(GitHub向けのIssueリーダー)というソフトウェアを開発しています。あと、CodeLunch.fmというポッドキャストをやっていたりもします。

僕個人に関してもっと詳しい話はForkwell Pressのインタビュー記事でお話させてもらっているので、興味のある方はご覧ください。


この連載を通して「仮説を素早くプロトタイプにしていく開発」や「React Nativeを使った開発」などに興味を持たれた方がいらっしゃれば、丸山(TwitterのDM)までお気軽にご連絡ください!もちろん、採用ページから応募していただくのも大歓迎です😊

最後に、この記事を読んだ印象を簡単なアンケートでご回答いただけるとうれしいです!

アンケートリンク

*1:ちなみに、プロダクションに使えるかどうかはどちらかわからないというのが当時に意見でした

*2:社内のエンジニア数名にも手伝ってもらいながら