筋肉にちゃんと「お願いマッスル!」するために、1からReact学んでアプリをリリースした話

2020年の秋に、個人的にReact（expo）でアプリを制作・リリースしました。
日頃の業務はHTMLのマークアップ・コーディング・JSはやってもバニラかjQueryなので、案件ではないとはいえ、初めてやってみてコンポーネントの概念など勉強になりました。
主に参考記事の抜粋が多いですが、アプリを作った中でつまづいたこと・学んだことを書きたいと思います。

# 作ったアプリについて

![pitch-mo-voice-counter.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40940/c50c3ab2-4a2a-c413-0438-cd8dafc09509.png)
 **ピッチモボイスカウンター**

※2021年末にこのアプリは非公開にいたしました。お試しいただいた方、ありがとうございました！

「1、2、3、1回」、「1、2、3、2回」...のようにピッチ（1、2、3のリズム）と回数の両方を声でカウントするアプリです。
![pitch-mo-voice-counter-phone.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40940/82a87f25-9d8f-780a-8679-f4f7f5468f30.png)

広告は一切入れておらず、完全無料です。
（お金儲けするつもりはなく、自分が筋トレできればいいし、広告を入れるとユーザー(私)の使う気が50%減るため）

全体的なソースコードはこちら→ https://github.com/sararilfy/pitch-mo-voice-counter/blob/develop/App.js

## なぜアプリを作ったのか

まず会社の健康診断で悪玉コレステロールの値が要精密だったという背景があり、どうにかして健康に戻したいと考えていました。そんな中、テレビの「金曜日のス●イルたちへ」という番組でヒットしている筋トレ本を知りました。
[はじめてのやせ筋トレ](https://www.amazon.co.jp/dp/4048964151/)
[やせ筋トレ 姿勢リセット](https://www.amazon.co.jp/dp/404896836X/)（第２巻）

この本で筋トレを習慣にしようとしました。
しかし、究極に体が硬い私は体を動かすので精一杯で、何秒経ったかなんて数える余裕がないのです。
そして体を動かすスピードが速すぎて筋肉に効いてなかったり、何回やったか忘れて途中で諦めてしまったり。

それを解決するべくボイスカウンターアプリを探しました（アプリをいちいち見るのも面倒だったためボイスアプリ）。
しかし、声でピッチ（ペース）の秒数と回数の両方教えてくれるアプリはありませんでした（どちらか片方対応、というのはあったんですけどね...）。

自分で作るか...と静的HTMLで音声読み込んで音が出るようにJSでプログラムを書いていたところに、プログラマーの夫が「これはアプリにしちゃえば？」と言われたので、アプリ化することにしました。

# Reactの基礎は、React公式のチュートリアルのみで学んだ

取っ掛かりは[React Native+Expoではじめるスマホアプリ開発](https://www.amazon.co.jp/dp/4839966648/)という本を参考にしました。
ただ、この本はExpoの概要的な本で、Reactの基礎は載っていなかっため、Reactの本を探しました。
が、本があまりない！（あっても載っている情報がだいぶ古いバージョンのもの）

...ということで、[React公式のチュートリアル](https://ja.reactjs.org/tutorial/tutorial.html)をやることにしました。
これがめっちゃわかりやすかった！！！なんて親切なドキュメントなんだろうと思いました。
チュートリアルを通したあと、[ドキュメントを1〜12まで](https://ja.reactjs.org/docs/hello-world.html)しっかり読み進めて、これだけでReactの概要は掴むことができました。

さらに、作り方を導いてくれる[Reactの流儀](https://ja.reactjs.org/docs/thinking-in-react.html)ページがあり、本当にこの通りの手順で形にしていきました。
最終的に、本は買わずに済みました。


# アプリデザインについて

[Reactの流儀](https://ja.reactjs.org/docs/thinking-in-react.html) によると、まずはモックアップが必要とのことだったので、デザインをつくることにしました。

### ▼デザインソフトは、慣れているAdobe XDで作成
![アプリデザインカンプ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40940/6f919afc-5df7-df8c-5263-fbfacb21d12a.png)

自分の理想を詰め込んだデザイン。

* 数字の入力は親指１本だけにしたいので、ピッカー。
* あとどのくらいで終わるかひと目でわかりたいので、円がアニメーションする形
* ボタンは押しやすいように下に配置

デザインの参考に[Dribbble](https://dribbble.com/)や[ピンタレスト](https://www.pinterest.jp/)で「workout app」とかで検索して見まくりました。

以下のリンクの「UIキット」も、アプリデザインの参考にしました。（余白の取り方、どう境目を作るかなど。）
[https://www.adobe.com/jp/products/xd/resources.html](https://www.adobe.com/jp/products/xd/resources.html)

あとは、Disabledボタンは薄くするといいとのことで、これも採用しました。
[UXデザイナーから学ぶ、無効状態のボタンをグレーアウトにしない理由 | コリス](https://coliss.com/articles/build-websites/operation/work/why-you-shouldnt-gray-out-disabled-buttons.html)

また、コンポーネントを考えるにあたって、[Atomic Designの記事](https://design.dena.com/design/atomic-design-%E3%82%92%E5%88%86%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%A4%E3%82%82%E3%82%8A%E3%81%AB%E3%81%AA%E3%82%8B/)を読み、単純にグルーピングするのではなく、原子レベルで小さく考える...ところとか参考にしました。

# [Reactの流儀](https://ja.reactjs.org/docs/thinking-in-react.html)をもとにしたアプリ構築の流れ

Expoの環境構築の後、このチュートリアルの手順でアプリを作りました。

## Step 1: UIをコンポーネントの階層構造に落とし込む

![コンポーネント設計のノート](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40940/46f13d45-4947-776c-5e01-35fe7837b57c.jpeg)

こう見返すと汚い（汗）
ノートに手書きで、ひたすらに原子レベルで名前をつけていきました。
初心者だからか、原子レベルを気にしすぎてコンポーネントを細かくしすぎました。
するとスタイル（デザイン）が調整しずらくなり、作りながらコンポーネントの断舎離（？）を繰り返していくハメ(?)になりました。

## Step 2: Reactで静的なバージョンを作成する

**一切 state を使わないでください**と書いてあったので、言われたとおりにpropsだけ使って構築しました。
ここまでで全く動かない見た目だけ綺麗なアプリができました。

## Step 3: UI 状態を表現する必要かつ十分な state を決定する

>
1. 親から props を通じて与えられたデータでしょうか？ もしそうなら、それは state ではありません
2. 時間経過で変化しないままでいるデータでしょうか？ もしそうなら、それは state ではありません
3. コンポーネント内にある他の props や state を使って算出可能なデータでしょうか？ もしそうなら、それは state ではありません

この３つの質問を投げかけながら、これまで作ってきたどのPropsをStateに変更すべきなのか、考えました。

最初は数字の部分は全部Stateだと思っていました。（毎秒数が変わるから）
しかし、毎秒カウントなどの任意のタイミングで数値を変更するという仕組みにしないといけない場合、親コンポーネントにstate置いて、残りはpropsでデータを下に渡していくべきだと気づいて、次のStep4で結構何度も設計し直しました。

## Step 4: state をどこに配置するべきなのかを明確にする

何度も何度も何度も作り変えて試行錯誤した結果、最終的にこのようなStateとpropsの構成になりました（一部）。
親コンポーネントのWorkoutVoiceCounterにStateをぜーんぶ持たせて、親コンポーネントの関数でStateの値を変更したあと、子コンポーネントにPropsで値を渡します。

### Stateとpropsの構成

![Stateとpropsの構成1](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40940/f1330524-8203-d3dc-63e7-00feb08c27e1.png)

### デザイン上はこんな感じ

![Stateとpropsの構成](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40940/3be18e9f-94fb-f9a5-5f8d-08dbfff80ffc.png)

振り返って見るとネーミングのばらつきが気になります...。

## Step 5: 逆方向のデータフローを追加する

今度の壁は、子や孫コンポーネントで変更があった場合に、どうやって親コンポーネントを変更するか？

難所だったのがコンポーネント構造の都合上、子コンポーネントだけでなく孫コンポーネントができてしまい、孫コンポーネントまでデータを渡したり、孫コンポーネントのボタンをタップしたら親コンポーネントのStateを変えたりしないといけなくなったことです。
苦労を重ねた結果、「バケツリレー」という結論にいたりました。

まず、子が何かしたときに親のStateにアクセスするためには、親に関数定義して、子に関数を渡す必要があるとのこと。
さらに子に関数を渡すときはバインドが必要と以下ページから学びました。
[ReactのDocs:コンポーネントに関数を渡す](https://ja.reactjs.org/docs/faq-functions.html)

今度は、親から孫コンポーネントに一気に関数を渡そうとしましたが、できず、めちゃめちゃググった結果、子にPropsを通した後に、子から孫へPropsで渡すというバケツリレーしてもらうしかないとたどり着きました。しかも関数の形で渡すんですね。
（以下のQiita書いてくれた人に感謝！）

~~Qiita: [Reactコンポーネント]孫から親のイベントを呼びたい時~~
記事がNot Foundになってしまいました。。。

関数は届いたけど、引数が届かないなんてこともあり、引数の枠も子へ孫へバケツリレーしないといけない！...こともありました。

最初は各コンポーネントごと計算する関数を入れ込んでいたのですが、最終的に全部親（祖父？）コンポーネントで計算処理して、結果データを子や孫に落としていく感じになりました。

以上、「Reactの流儀」はここまでで、あとは欲しい機能をひたすら追加していくのみでした。

# React (expo)でハマった問題と対処集

完成するまでに何度も分からなかったり、何度もエラーにハマったり、壁にぶち当たったりました。
その時どう解決したかを記載していきます。

## 今回大きく学んだこと1 「チュートリアルは必ず最初に触って、Docsにも目を通しておこう」

今思えばエンジニアとしてあるまじき行為...。

私は最初、expoの本を借りて、expoの導入の仕方を習ったのと、「Stateをあれこれするんだよ」っていう口頭のアドバイスのみでReactに取り組んでしまい、基本を理解せずに本やネットのソースコードをコピペして「動いたー！」...という進め方をやってしまいました。

ある程度までは形になったものの（よく行けたな...）、コンポーネントは肥大化、renderって何？どこに処理書いたらいいんやろ？…。案の定、途中で完全に行き詰まってしまい、「Reactをゼロから学んで作り直そう...」と１からやり直す羽目になりました。

その時、企画からすでに半年たっていましたが、Reactをきちんと学んでからは一気に3ヶ月で完成に至りました。
学び直す前は同じ記述のソースコードが何回も何回も並んでいましたが、それも無くなってスッキリ読みやすくなりました。
Reactが分かっていくたびにアプリづくりも楽しくなりました（学生で嫌いな教科がわかり始めて好きになる感じ）。

すごく当たり前なのですが、新しいものを触る時はドキュメント読んで、チュートリアルくらいは触ろう！...と過去の自分に言いたいです。
React甘く見過ぎでした。

## 今回大きく学んだこと2 「Stack Overflow は頼りになる。英語で検索」

アプリ制作中何度もエラーに遭遇して、すごく助けてくれたのが[Stack Overflow](https://stackoverflow.com/)でした。
そして英語で検索したほうが、ほしい回答にたどり着けました。英会話に通っていたおかげで、抵抗なく英語で検索できました（通っていて良かった！）。

例) 「XXXが動かない」 → 「XXX is not working」

英単語をつなげるだけでも、検索結果にでてきてくれました。
このコツを習得しただけでも（私の中で）大きい気がします。
ただ、読むときはまだGoogle翻訳だよりです(汗)

## Pickerが動かない

悩んだランキング第3位。
[RactNativeでPickerを利用する
](https://qiita.com/zaburo/items/e37189170747e4521152)を読みながら、Pickerを実装できるところまでは行きました。
しかし、Pickerは合計５個並ぶので、Pickerを５回書くのではなく、Pickerコンポーネント1個だけにして、配列でそれぞれ別の値をもたせてテンプレートのように使いまわしたかったのです。

あれこれ変更してみても動かない...試行錯誤して、以下の「繰り返しはtoString()が必要だった」という結論にたどり着きました。
[React Native で Pickerのitemをmap()で簡潔に実装する方法](https://qiita.com/progriro/items/de6c2f7516663c2beff1)

```
<Picker.Item
 key={i}
 label={i.toString()}
 value={i}
/>
```

## ボタンを押したときに、背景色を変更したい
親コンポーネントに「backgroundColor」というStateを準備すれば可能でした

```react
<View style={[styles.background, {backgroundColor: this.state.backgroundColor}]}>
</View>
```
参考: [React Native Dynamically Change View’s Background Color
](https://stackoverflow.com/questions/41988305/react-native-dynamically-change-view-s-background-color)

## ダークモード対応
参考1: [Appearance - Expo Documentation](https://docs.expo.io/versions/latest/sdk/appearance/)
参考2: [【React Native】Expo SDK 35で大雑把にダークモードに対応した時のメモ](https://ru-blog.netlify.app/react-native-darkmode/)

ダークモードに切り替えたときに瞬時にチェンジするのは、`componentDidMount`の中に関数を書いてできました！
ただ、色を全部State設定にする必要があったのがちょっと面倒でした。
以下、ソースコードの抜粋。

```react
const
    THEME_COLORS = {
        light: {
            textColor : "#333333",
            backgroundColorSetting : "#f1f0f2",
            backgroundColorCount : "#ffffff",
            backgroundColorPickerCard : "#ffffff",
            colorMain : "#f87c54",
            colorInterval : "#4ac08d",
            colorBackgroundCircle : "#e6e6e6",
            statusBarColor : "dark-content"
        },
        dark: {
            textColor : "#dddddd",
            backgroundColorSetting : "#1a2744",
            backgroundColorCount : "#1a2744",
            backgroundColorPickerCard : "#424e69",
            colorMain : "#fa8f6d",
            colorInterval : "#72C19f",
            colorBackgroundCircle : "#424e69",
            statusBarColor : "light"
        },
    };

class WorkoutVoiceCounter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            backgroundColor: THEME_COLORS[colorSchemeName].backgroundColorSetting,
            mainKeyColor: THEME_COLORS[colorSchemeName].colorMain,
            textColor: THEME_COLORS[colorSchemeName].textColor,
            backgroundColorSetting: THEME_COLORS[colorSchemeName].backgroundColorSetting,
            backgroundColorCount: THEME_COLORS[colorSchemeName].backgroundColorCount,
            backgroundColorPickerCard: THEME_COLORS[colorSchemeName].backgroundColorPickerCard,
            colorMain: THEME_COLORS[colorSchemeName].colorMain,
            colorInterval: THEME_COLORS[colorSchemeName].colorInterval,
            colorBackgroundCircle: THEME_COLORS[colorSchemeName].colorBackgroundCircle,
            statusBarColor: THEME_COLORS[colorSchemeName].statusBarColor
        };
    }

    _setModeColor = (colorScheme) => {
        this.setState({
            textColor: THEME_COLORS[colorScheme].textColor,
            backgroundColor: THEME_COLORS[colorScheme].backgroundColorSetting,
            backgroundColorSetting: THEME_COLORS[colorScheme].backgroundColorSetting,
            backgroundColorCount: THEME_COLORS[colorScheme].backgroundColorCount,
            backgroundColorPickerCard: THEME_COLORS[colorScheme].backgroundColorPickerCard,
            colorMain: THEME_COLORS[colorScheme].colorMain,
            colorInterval: THEME_COLORS[colorScheme].colorInterval,
            colorBackgroundCircle: THEME_COLORS[colorScheme].colorBackgroundCircle,
            statusBarColor: THEME_COLORS[colorScheme].statusBarColor
        });
    }

    async componentDidMount() {
        Appearance.addChangeListener(({ colorScheme }) => {
            colorSchemeName = colorScheme;
            this._setModeColor(colorScheme);
        });
    }
}
```

カウントが走っている中にダークモードに変更すると、以下エラーが出てしまうので、カウント中はSetStateしないようにif文でフラグを用意したり、最初の設定に来たら必ず今ダークモードなのかチェックを走らせるように工夫しました。

> Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in %s.%s, the componentWillUnmount method,

## 音声の問題
悩んだランキング第2位。
そもそも、Expoで音をどう出すのかが分からない。
ずーーーーっとググってて、やっとこの記事にたどり着いて、真似したらやっと音出たー！
（ここからやっと１歩すすんだのです、感謝！！！）
[【ReactNative】【Expo】画面遷移のタイミングで音を鳴らす方法](https://qiita.com/wafuwafu13/items/0836386c8c46260fa8a5)

最終的に、以下ソースコードを参考にしてやっと出来上がりました。
[expo/playlist-example](https://github.com/expo/playlist-example)

## 同じ音を流すと2回目からエラーで流れない
[AV - Expo Documentation](https://docs.expo.io/versions/latest/sdk/av/)
[Audio - Expo Documentation](https://docs.expo.io/versions/latest/sdk/audio/)
上記のページを何度も読み込みました。なんかよくわからなかったのですが、.unloadAsync()と.replayAsync(0)組み合わせるなど試行錯誤した結果、同じ音声だったとしても、音声を出すたび毎回ロードすることになりました。

## たくさん音源があるので、スッキリまとめて記述したい
「いち」「にー」「さん」...「じゅっかい」など数を言わないといけないので、音声ファイルが29ファイルありました。
変数とかにいれて、いい感じにスマートに書きたかったのですが、
[react-nativeでiOSアプリを作ってみた時のエラーあれやこれ
](https://masaru-tech.hateblo.jp/entry/2017/02/07/214549)
上記記事にRequireは変数にできないと書いてある…。

また、[How to get absolute path of a file located in the assets folder with react-native and expo?](https://stackoverflow.com/questions/56261309/how-to-get-absolute-path-of-a-file-located-in-the-assets-folder-with-react-nativ)を参考に、[Asset - Expo Documentation](https://docs.expo.io/versions/latest/sdk/asset/)を導入していたのですが、音が遅れる問題が発生したので、Assetをアンインストールして、createAsyncじゃなくて、loadAsyncで相対パス指定。記述は増えたが、これじゃないと途切れてしまう。。。（今毎秒ごとにhttp読み込んでるため？）
スマートに書くのは諦め、地道に書くことに。
以下ソースコードの抜粋。

```react

const soundPath = "./assets/sounds/";

_loadSound = async (isNumber, soundLabel) => {
    if (this.playbackInstance != null) {
        await this.playbackInstance.unloadAsync();
    } else {
        this.playbackInstance = new Audio.Sound();
    }
    switch (soundLabel) {
        case 1:
            await this.playbackInstance.loadAsync(require(soundPath + "info-girl1_info-girl1-ichi1.mp3"));
            break;
        case 2:
            await this.playbackInstance.loadAsync(require(soundPath + "info-girl1_info-girl1-ni1.mp3"));
            break;
        case 3:
            await this.playbackInstance.loadAsync(require(soundPath + "info-girl1_info-girl1-san1.mp3"));
            break;

     // 以下同じ記述が26個つづく

    }
}
```

## 円アニメーションどうやったらできるんだ問題

![円](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40940/a8c6a043-09fa-2993-80f5-4a97c04002a2.png)

悩んだランキング第１位。

ひとまず、SVGで静的な円を書くところまでは`react-native-svg`を導入していけました。
参考1: [Svg - Expo Documentation](https://docs.expo.io/versions/latest/sdk/svg/)
参考2: [Github react-native-svg/react-native-svg #circle](https://github.com/react-native-community/react-native-svg#circle)
参考3: [10分でわかるSVG 基礎編](https://www.atmarkit.co.jp/ait/articles/1206/01/news143.html)

ここからが問題。どうやってアニメーションさせるんだ...？
ここ何日も何日も悩んでいました。

Youtubeに海外の人がReactのコーディング動画をあげていて、参考にしようと見るとsign・cosign・tangent...。高校生で習ったけど忘れた...分からない。

出口が見えないまま、ある日ふと以下記事を見る。
[SVGで円をアニメーションさせたい時のMEMO](https://qiita.com/melo15/items/6cc3c24e448a8fb8492c)

`stroke-dasharray`...？......( ﾟдﾟ)ハッ!

`react-native-svg`で`stroke-dasharray`は......表現できることを確認！
`stroke-dasharray`をStateで変化させていけば良いのでは......とやってみたところ、
うおあー！できたあああああCircleのアニメーション！！！！

上の記事に感謝！！！ 
Stateすればことに何で今まで気がつかなかったんだー！
悩んだ割には、やり方は単純でした。

## iPhone実機で表示確認するには？
表示確認は[Expo Client](https://apps.apple.com/jp/app/expo-client/id982107779)アプリで行いました。
PCのシュミレーターではいい感じでも、iPhoneで見てみると、ボタンの文字が切れていたり、文字によって表示にガタつきがあったりしました。やはり実機で見ないといけないですね。

## スプラッシュスクリーン作って読み込んでみた
スプラッシュスクリーンはアプリのLoading画面にも使えたので、読込中は背景色もオレンジに変更しました。
画像は `resizeMode` を `cover` に設定して余白が出ないようにしました。
参考1: [Create a Splash Screen - Expo Documentation](https://docs.expo.io/guides/splash-screens/)
参考2: [SplashScreen - Expo Documentation](https://docs.expo.io/versions/latest/sdk/splash-screen/)


```app.json
{
  "expo": {
    "name": "ピッチモボイス",
    
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "cover",
      "backgroundColor": "#f87c54"
    }
  }
}
```

## エラーが起きた時にログをメールで飛ばしたい
Bugsnagというものを見つけました。
https://www.bugsnag.com/

Expoのドキュメントにも導入の仕方が載っていました。
https://docs.expo.io/guides/using-bugsnag/
Bugsnagの導入説明書
https://docs.bugsnag.com/platforms/react-native/expo/

新規登録して、マイページからAPPKey探して、あとはインストールのコマンド打ったら案内してくれたので思ったより導入が楽でした！

## PCでiOSのシュミレーターを立ち上げても「There was a problem...」で表示されない
以下サイトに助けてもらいました。
iOSシュミレーター内のiPhoneでアプリをインストールしなおさないといけないとのこと。
iOSシュミレーターの動かし方もググった記憶があります。
参考: [Expo Clientアプリにプロジェクトが表示されないときの解決方法](https://www.aizulab.com/blog/project-not-shown-in-expo-client-app/)

## iPhoneSEの小さいデバイスのシュミレーターに変更したい
小さいデバイスでもちゃんと収まるか表示確認したかったので、シュミレーターをiPhoneSEにできないか調べた。
参考1: [Is there an iPhone SE simulator for Xcode 11, iOS 13?](https://stackoverflow.com/questions/58071781/is-there-an-iphone-se-simulator-for-xcode-11-ios-13)
参考2: [Expo: Change default IOS simulator
](https://stackoverflow.com/questions/47709953/expo-change-default-ios-simulator)

上記stack overflowによると、Expoは最後に開いたシミュレーターが次回のシュミレーターになるそうで、

1. いつも通りシュミレーターを立ち上げているときに、 [ ハードウェア] > [デバイス]をクリックして、目的のデバイスを選択
2. これで2つのシミュレーターが開く。ほしいシミュレーターがアクティブであることを確認する
3. Command + Qでシミュレーターアプリを終了
4. もう一度コマンド「i」でシュミレーターを立ち上げれば、欲しいシュミレーターの方が出るはず


## カウント中、勝手にiPhoneがスリープしちゃう問題
KeepAwakeという便利なものを発見して導入しました。
[KeepAwake - Expo Documentation](https://docs.expo.io/versions/latest/sdk/keep-awake/)

## 前回実行した筋トレの設定（何分何セット何秒...）の保存をしたい

AsyncStorageの使い方は理解したものの、Pickerの値が変わりませんでした。
孫コンポーネントにcomponentDidMount追加してデータ呼び出してもダメでした。。。

どうやら、データが来る前に描画が走ってしまうっぽいので、データを取得してから描画に行くように、LOADING画面を追加しました。
以下、参考記事です。
[React Native AsyncStorage fetches data after rendering
](https://stackoverflow.com/questions/33553112/react-native-asyncstorage-fetches-data-after-rendering)
デバッグのために調べた記事
[JavascriptのalertでObjectの中身を表示する方法](https://hacknote.jp/archives/26266/)
Reactのライフサイクルを学びなおした記事
[React(v16.4) コンポーネントライフサイクルメソッドまとめ](https://qiita.com/Julia0709/items/3c3fc8d29fd2e56ed7a9)

## アプリにFirebase導入

[expo-firebase-analytics](https://www.npmjs.com/package/expo-firebase-analytics)を使って、以下記事を参考に実装することができました。
つまづきは、Npmのバージョンをあげないといけなかったくらいだったはず。

参考記事一覧:
[expo-firebase-analytics: is there a way to enabled debugView? #7745
](https://github.com/expo/expo/issues/7745)
[Using Firebase Analytics with Expo
](https://blog.expo.io/using-firebase-analytics-with-expo-222ca84e4d33)
[Using the native Firebase SDK](https://docs.expo.io/guides/setup-native-firebase/)
[FirebaseAnalytics](https://docs.expo.io/versions/latest/sdk/firebase-analytics/)

主な流れは以下。

1. Firebaseプロジェクト作る
2. プロジェクト内でIOSアプリを追加、
3. 同じプロジェクト内でWebアプリを追加（これでスタンドアローン？をビルドしなくてもアナリティクス計測できた）
4. App.jsonにWebアプリのもろもろを書き加える
5. Promiseでイベント送信するコードを入れる
6. expo-firebase-analyticsのバージョンをあげる
7. デバックモードでテスト
8. expo-firebase-analyticsのバージョンをもどす(expo install expo-firebase-analytics)

アプリをどう使われているか知りたかったので、イベントを導入することに。
以下記事でFirebaseについて学ぶ。

[無料で使えるモバイルアプリのデータ計測ツール「Google アナリティクス for Firebase」って何？](https://webtan.impress.co.jp/e/2020/01/10/34458)
[Google アナリティクス for Firebaseレポートの見方と10種類のレポートを解説](https://webtan.impress.co.jp/e/2020/04/01/35405)

ほしいイベントをノートに書き出して...

![イベントの設計ノート](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40940/1a53d051-dfb0-7d6e-db70-38489f369264.jpeg)

イベントを設定して、デバッグモードで検知するかテスト。
デバッグモードにするときは、以下を記述しました。

```
Analytics.setDebugModeEnabled(true);
```

デバッグモード中のFirebaseの画面
![Firebaseの画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40940/767aed82-e94a-4a2e-7aac-01c5fe25b644.png)


## テストフライト
β版テストができると初めて知りました。ここは悩むことなく実行できました。
https://testflight.apple.com

## アプリをAppleに申請
何回も設定パターンをテストして、大丈夫！リリースしよう！ってなった時、Google翻訳を使って、まず読み込んだのが以下Expoのドキュメント。
[Deploying to App Stores - Expo Documentation](https://docs.expo.io/distribution/app-stores/)

以下2記事は一番参考にしました。
[AppStoreの審査でリジェクトされない登録方法 | iTunes Connect](https://itblogdsi.blog.fc2.com/blog-entry-190.html)
[[iPhone] App Store Connect にアプリ情報を登録して申請してみる](https://i-app-tec.com/ios/itunes-connect.html)

アプリ申請のときに記入欄が多く見えて絶望を少しだけ感じましたが、時間かかりながらもなんとかできました。

### アプリ申請でやったこと一覧

- [x] App.jsonをちゃんと埋める。 参考: [app.json / app.config.js - Expo Documentation](https://docs.expo.io/versions/latest/config/app/)
- [x] Apple Developerに登録（お金を払う）
- [x] スタンドアローン構築とテスト。publish後もテスト。 参考: [Building Standalone Apps - Expo Documentation](https://docs.expo.io/distribution/building-standalone-apps/)
- [x]  `expo upload:ios --latest`でアップロードする。 参考: [Uploading Apps to the Apple App Store and Google Play - Expo Documentation](https://docs.expo.io/distribution/uploading-apps/)
- [x] 自分のブログにプライバシーポリシー公開と、お問合せ先メールアドレスを取得して記載
- [x] スクリーンショット画像を作成。...5.5インチも6.5インチも両方準備しないといけなかった。。。

他に参考にさせていただいた記事（感謝！）

* [非iOSエンジニアがExpoのReact Native(CRNA)で作ったアプリを初めてAppleに申請するまで](https://qiita.com/HorieH/items/9d4a19377c0f0ab014ba)
* [[iPhone] アプリ申請のためXcodeでアップロードする](https://i-app-tec.com/ios/app-upload.html)
* [【完全手順】iOSアプリ開発後のAppStore審査申請提出から通過まで](https://taupe.site/entry/ios_app/)
* [【2019年版】iOSアプリをApp Storeに公開するための全手順まとめ](https://qiita.com/kenny_J_7/items/92378e2a09bee6080677)
* [【アプリ申請】必見！一発でiOSアプリ審査を通すためのチェックリスト](https://www.bravesoft.co.jp/blog/archives/749)

今後翻訳するんだったら、言語設定は日本語にしないほうが良いと書いてありましたが、日本語の音声準備するのがものすごく大変だった（Adobe Auditionで編集しまくった）ので、もう海外対応までやらないだろうと日本語オンリーにしました。

工夫したことは、一通り使用する場面を録画した動画も添付したり、メモ欄にちゃんと使い方などを英語でも書いたことです。
動画は畳の上でやったので、畳がバッチリ写ってたのですが...伝わったんでしょうか。１発OKで通りました。

以下、アプリ申請時のメモ欄に記載したもの。

```text
————————————————
日本語
————————————————

【使い方】
1. 初期設定画面で次のように設定し、「スタート」を押します。
・目標
  「3」回、「3」セット
・インターバル時間
「1」分「00」秒
・間隔（ピッチ）
「3」秒

2. 「5、4、3、2、1」と5秒前からカウントダウンの後、カウントが始まります。
「1、2、3、1回」
「1、2、3、2回」
「1、2、3、3回」
...のように声と表示でカウントします。

3.  1セット目のカウントが終わると「休憩」の声の後、設定したインターバル時間をカウントダウンします。
インターバル時間が終了に近づくと、
「5、4、3、2、1」と5秒前に声のカウントダウンの後、自動で2セット目のカウントが始まります。

4. カウントが終了したら「設定へ」ボタンを押すと、最初の設定画面へ戻ります。

5. カウント中に一時停止したい場合は「一時停止」ボタンを押し、
途中で終了したい場合は「キャンセル」を押してください。

6.最初の設定で、インターバル時間「0分00秒」に設定した場合は、1セット目が終了したときに、インターバルカウントせずにすぐ2セット目のカウントがスタートします。

【注意点】
・消音モードは解除してください。また、イヤホンよりもスピーカーを推奨します｡
・ダークモード対応ですが、カウント中にモードを変更すると、設定画面に戻るまで変更されません。
・一時停止のまま10分放置すると自動でカウントがキャンセルされて、最初の設定画面へ戻ります。
・このアプリでは以下の素材を使用させていただいております。どちらも商用利用可能な無料素材です。
・写真素材 足成
http://www.ashinari.com/
・声素材 効果音ラボ
https://soundeffect-lab.info/

————————————————
English
————————————————

【How to use】

1. Please set as follows. And push ‘スタート’ button.
・目標
  「3」回、「3」セット
・インターバル時間
「1」分「00」秒
・間隔（ピッチ）
「3」秒

2. After saying countdown「5、4、3、2、1」, this app starts count.
「1、2、3、1kai」
「1、2、3、2kai」
「1、2、3、3kai」
This app counts like this by voice.

3.  When finishing first set, this app says ’Kyukei’. And this app counts down the first set interval time. This example time is 1 minute.
When the interval time approaches the end, app counts down 「5、4、3、2、1」and 
 Starts counting second set.

4. When the count is over,  Press the ’設定へ’ button to return to the initial setting screen.

5. If you want to pause count, please press ‘一時停止’ button.
If you want to cancel count, please press ‘キャンセル’ button.

6. When the interval time is set to "0" in the first setting, After finishing first count, app skips interval time and starts second count.

【Cautionary point】
・Please cancel silent mode. And I recommend speakers over earphones.
・This app dark mode support, but if you change mode while counting, app will not change until you return to the setting screen
・When you continue for 10 minutes, app cancels the count and return to the initial setting screen automatically.
・This app use those free image and sounds. Both are available for commercial use.
- Picture Ashinari
http://www.ashinari.com/
 - Sounds Soundeffect-lab
https://soundeffect-lab.info/

```

# おわりに

筋トレするときはいつもアプリを使っています。これで筋トレ何回何秒やったか忘れたから途中でやめちゃうなんてことはナシです！！
完全に個人的に作った我流のソースコードなので、たくさん「え？」って思われる実装がたくさんあると思いますが、Reactがどういうものなのかを知ることができて良かったです。
JavaScriptもたくさん調べて、クラス・インスタンス・コンストラクタなど、基礎的なことを学び直すきっかけにもなりました。
そして、これから筋トレを続けて、次の健康診断で悪玉コレステロールの値が下がっていますように...！（＞人＜ ；）