開発合宿でVue.js+Firebase+Onsen UIで食事の栄養管理アプリを作った話

# はじめに
この記事は、[ZOZOテクノロジーズ アドベントカレンダー#4](https://qiita.com/advent-calendar/2019/zozo_tech4)の記事です。

こんにちは！20卒内定者のきょっぴー(@kyou13)です。
先日、ZOZOテクノロジーズの社員がメンターとなる形で内定者合宿が行われました。今回はその内定者合宿で作ったものについて説明していきたいと思います。

# 内定者合宿について
今回の内定者合宿は湯河原にある[おんやど恵](http://www.onyadomegumi.co.jp/)で11/18(月)〜19(火)の1泊2日の日程で行われました。こちらは[7月にZOZOテクノロジーズの社員で行われた開発合宿](https://techcorp.zozo.com/entry/20190711__Development_camp)と同じ場所です。
温泉はもちろんのこと、足湯もあるため、足湯に浸かりながら開発もできます。

この内定者合宿のテーマは
**「日常で感じている課題を（エンジニアリング・デザイン・ビジネスを使って）解決するアイディアを考えてみよう！」**
というものでした。このテーマに沿って個人開発を行います。

スケジュールとしては、1日目の10時過ぎに宿に到着し、午前中はオリエンテーション、午後から開発スタート。2日目は9時から発表という流れです。

# 開発スタート
今回のテーマをもとに、ある程度趣味などに関連するものがいいと考えました。私は筋トレが趣味で、効果を最大化するために食事の栄養価が気になってしまいます。なので**手軽に食事の栄養管理ができるアプリ**を作成することに決めました。
具体的には、スマホのカメラで食品のバーコードをスキャンし、カロリーと三大栄養素（たんぱく質・脂質・炭水化物）を記録できるようにします。

## 要件定義
大まかな仕様はざっくりと以下のようにしました。

- スマホのカメラでバーコードをスキャンし食事を記録
- 1日の摂取カロリー・栄養素をグラフで可視化
- ユーザの年齢、性別を設定できるようにする(1日の摂取目安が変わるため)
- ログイン機能でユーザごとにデータを管理

時間的制約があるため、いかに効率よく開発を行うかがキモになってきます。

## 技術選定
仕様と合宿開始以前の開発経験をもとに、技術選定を行います。
プッシュ通知を実装してみかったので、ネイティブアプリとして開発したかったのですが、これまでネイティブアプリの開発経験がないことと、時間的制約のため、見た目だけネイティブ風のWebViewで動くアプリを作成することにします。

バックエンドに関しては**Firebase**を使います。今回、ユーザを識別するためにログイン機能が必要となります。この認証は**Firebase Authentication**を利用しようと考えました。また、データベースやホスティングも提供されているため、バックエンドの開発コストを下げることができるために使用します。

## 成果物
以上を踏まえてできたものが以下です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133979/bdcfc625-bc7b-15f7-d0a9-60edff9fdafa.gif" width=50%>

- 下部メニューバー
  - 記録した食事(リスト表示)
  - 一日の摂取目安に対する割合(チャート表示)
  - 設定画面

### バーコードスキャン画面
- バーコードをカメラで読み取ることで、手動でひとつひとつの栄養価を入力する手間が省けます
- (バーコードのない商品でも記録できるように"バーコード<->物体検出"のトグルをつけたが、結局物体検出の方は実装できなかった..)

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133979/1b9cfb71-427f-8397-0d32-d4a3170955ca.jpeg" width=45%>

### リスト表示
- 日毎に記録した食品リストが表示されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133979/b6567a34-37cf-2660-e277-927d624497bd.png" width=45%>

### チャート表示
- 現時点での栄養価が１日の目安に対する割合で表示されます。チャート表示にすることで、どの栄養価が足りないか多いかが視覚的にわかりやすいです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133979/19d35690-3c07-7e94-6807-3ef69197be9b.png" width=45%>


## アーキテクチャ

### 全体像
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133979/79289e7c-cae2-af21-1277-b780b32a120c.png" width=90%>



### フロントエンド
- **Vue.js**
    - コンポーネントライブラリ：[Onsen UI 2](https://ja.onsen.io/)
    - バーコードリーダライブラリ：[QuaggaJS](https://serratus.github.io/quaggaJS/)

今回はWebViewでネイティブアプリ風を実現するため、コンポーネントライブラリとして**Onsen UI 2**を使いました。
Onsen UIはネイティブアプリ風のUIコンポーネントを提供しており、見た目はもちろんのことアニメーションもネイティブっぽくすることが可能です。また、OSによってUIを切り替えることもできたり、Vue.jsにも対応していることからこちらを採用しました。

バーコードリーダライブラリには**QuaggaJS**を使用しています。これにより、ブラウザで動作するバーコードリーダを実装することが可能となります。バーコードのデコーダ形式にも様々対応しており、読み取り時のアニメーションなども設定できます。

### バックエンド
- **Firebase**
    - Firebase Realtime Database
        - リアルタイムでデータ更新・同期を行うデータベース
    - Firebase Authentication
        - ユーザ認証
    - Firebase Hosting
        - ホスティング

- **栄養価API**
    - AWS Lambda
    - Amazon API Gateway
    - Yahoo!ショッピング商品検索API
    - 食品成分データベース

#### Firebase
Firebaseのおかげでとても簡単にバックエンドを開発することができました。
##### Firebase Authentication
スクラッチで実装すると難しいユーザ認証もFirebase Authenticationを使うことでとても簡単に開発することができます。
メールアドレスとパスワードによる認証はもちろん、電話番号や、SNSアカウントによる認証も可能です。認証方法ごとに指定されたメソッドを使えばよいので、内部の認証方法については考える必要はありません。

今回はTwitterアカウントを使った認証にしており、以下のようなメソッドをログインボタンが押されたときの処理に対応させるだけで実装ができます。
ログインボタンが押されると、ポップアップウィンドウでログインが可能です。

```js
    doLogin() {
      // Tiwtterプロパイダインスタンス生成
      const provider = new firebase.auth.TwitterAuthProvider()
      // 認証処理
      firebase
        .auth()
        .signInWithPopup(provider)
        .then(() => {
          // ログイン成功時の処理
        })
        .catch(error => {
          // ログイン失敗時の処理
        })
    }
```

##### Firebase RealTime Database
NoSQLデータベースにjsonでデータを保存します。またすべてのデバイス間でリアルタイム(数ms以内)でデータ同期が可能です。
データ構造は以下のようになっており、ユーザごとにスキャンした食品を保存しています。

```json
{
  "recodeList": {
    "(uid)": [
      {
        "foodName": "カップラーメン",
        "calorie": 448,
        "fat": 19.7,
        "protein": 10.7,
        "carbo": 56.9, 
        "createdTime": (TimeStamp)
      }
    ]
  }
}
```


データの取得と書き込みは以下のコードで実装を行っています。

```js
  // databaseインスタンス生成
  this.database = firebase.database()
  // ログイン中のユーザ取得
  let uid = firebase.auth().currentUser.uid
  this.recodeListRef = this.database.ref('recodeList/' + uid)

  // データ取得
  this.recodeListRef.on('value', snapshot => {
    this.$store.commit('setRecodeList', { recodeList: snapshot.val() })
  }

  // データ登録
  addItem(val) {
    this.recodeListRef.push({
      name: val.category_name,
      calorie: val.calorie,
      protein: val.protein,
      carbo: val.carbo,
      fat: val.fat,
      created_time: Date.now()
    })
  }
```

`this.recodeListRef = this.database.ref('recodeList/' + uid)`によって、指定のコレクションに対して、データの取得や登録を行うことが可能となります。
`this.recodeListRef.on('value')`でデータの取得、`this.recodeListRef.push()`でデータの登録を行います。このようにデータベースの操作も簡単に数行で実装することができます。

##### Firebase Hosting
カメラを取得するためのgetUserMediaはhttps通信かlocalhostじゃないと使えません。発表時のデモで動けば良いので、localhostでも良かったのですが、せっかくならFirebaseのホスティング機能を使ってみたいと思いhttps化を行いました。

#### 栄養価API
フロントから渡された、バーコード情報をもとに栄養価を返すAPIです。
APIの流れ
1. バーコード情報をAPI GateWay経由で受け取る
2. バーコード情報をYahoo!ショッピング商品検索APIに渡し、その商品が属するカテゴリ情報を取得
3. 食品成分データベースからカテゴリ情報に一致する食品の栄養価を取得

バーコード情報と商品情報が対応したデータベースは個人利用が難しそうだったため、**Yahoo!ショッピング商品検索API**を使いバーコードから商品カテゴリを特定しています。


# おわりに
なんとか食事の栄養管理アプリが完成し、2日目の発表でデモと発表を行いました。その結果、景品として**レッドブル(24本)**をいただきました!!

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">内定者イベントの景品のレッドブルを研究室に寄贈した🍾<br>後輩たち大喜びでガブ飲みです！<br>💪👁👄👁💪 <a href="https://t.co/X4Z3vcASb2">pic.twitter.com/X4Z3vcASb2</a></p>&mdash; Kyoppy(@kyoppy_jp) <a href="https://twitter.com/kyoppy_jp/status/1199936878180352002?ref_src=twsrc%5Etfw">November 28, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


Firebaseを使うことによって、効率的に開発ができたと思います。このぐらいの小規模な個人開発だとFirebase+Vue.jsという組み合わせは、コストとスピードの点でとても良いなと思いました。

Firebaseは紹介した以外にも、分析基盤なども充実しており、デプロイ後の施策という部分でもうまく活用ができそうなので、引き続きキャッチアップしていきたいと思います。