React Nativeでアプリ開発前に知りたかったこと


React Nativeでアプリをリリースして、作る前に知りたかったRN特有の難しさをシェアです〜



記事の内容の前に宣伝です!!
摂取カロリー・栄養素管理アプリをリリースしました!

少しでも興味がある方はインストールしてみてください〜✌️

iOS: https://apps.apple.com/jp/app/silent-flame/id1556855885
Android: https://play.google.com/store/apps/details?id=com.silentflame
使い方: https://note.com/esuke/n/n9f64c4d6c112


環境について

内容に入る前に、今回のReact Native(RN)の開発環境について、

  • react-native-cliで作成
  • Package Version
    • react: "16.13.1"
    • react-native: "0.63.4"
    • typescript: "4.1.3"
    • react-native-firebase: "10.7.0"
  • other
    • Functional Component
    • tsx
    • mac

ライブラリ導入関連

ライブラリの追加でエラーがでがち

新しいライブラリを入れた時に結構な頻度でエラーが出ます。
私の場合は、どうやらストレージ管理に使おうと思っていたReamでいつもエラーが出ていました。
原因はバグのようでしたが、アンインストールした後もちょこちょこエラーが出ていました。

エラーが出た場合は下記のコマンドで再インストールすることで、エラーが解決できます。
package.jsonに登録するととても便利でした。

RN

rm -rf node_modules && rm yarn.lock && yarn && yarn start -- --reset-cache

iOS

まず、xCodeで"Clean Build Folder"を実行、

その後コマンドを実行。

rm -rf ios/Pods && rm ios/Podfile.lock && npx pod-install && yarn ios

Android

rm -rf android/app/build && cd android && ./gradlew clean && yarn android

それでも解決しない場合は、xCode, Android Studioでエラー内容を確認し、解決する必要があります。
例えば、info.plistやbuild.gradleでの記述漏れなどがある場合です。

Manual Linkingをしないほうがいい

特別な理由がない限りManual Linkingをしないほうがいいです。
私がRNを初めて触ってから2年くらいたち、久しぶりに触りましたが、React Native 0.60から実装されたAutolinkingが便利すぎます。

Autolinkingのおかげで、react-native-cliでの開発が一気に楽になった気がします。
多くのライブラリはAutolinkingに対応していますが、中にはメンテがされていないせいで、対応していないライブラリがあります。

Autolinkingに対応していないライブラリを使いたくて試しに、Manual Linkingの方法でインストールしましたがうまく入らず、断念しました。
また、Firebase関連のライブラリは不安定という話をよく聞くので、Manual Linkingしてインストールしてみましたが、うまく動かず、Autolinkingの方法で再インストールしてうまく動くようになりました。

特別な理由がない限り、Autolinkingに対応しているライブラリをAutolinkingの方法でインストールするのが良さそうです。

プラットフォーム間の違い

Buttonは使いにくい

iOSとAndroidで表示が変わるため、デザインをプラットフォーム間で同じにする場合は使う必要はなさそうです。

iOS

Android

TouchableOpacity、TouchableHighlightでイベントが取れないケース

VSCodeを使っている場合に起きがちなのかもですが、パッケージを自動的に追加した際に、

import {TouchableOpacity, TouchableHighlight} from "react-native-gesture-handler"

が追加されていることがあります。

想定していた追加はこっち

import {TouchableOpacity, TouchableHighlight} from "react-native"

react-native-gesture-handlerでは、ネイティブ側で処理されるため、onPressが取れません。(iosが取れるが、androidは取れない)

特にボタンを押した時に挙動が関係ないような、遷移用のボタンなどは、react-native-gesture-handlerに同じコンポーネントが存在しないPressableが紛らわしくなくていいでしょう。

androidはタブレットに対応しないといけない

今回、iOSを先行して開発していて、Android側の挙動は確認していませんでした。
iOSが完成して、さくっと出せそうならAndroidもだそうと思い、挙動を確認したところ、特に問題がなく、意気揚々とリリースしようとしたら、、、
google play consoleで、タブレット用のスクリーンショットが必要と警告が出ていました。

iOSではipadの対応を切っているため、iPhoneの多少違う幅と高さに対応しただけだったので、タブレットで表示するととてもみれるものではなかったです😅

とりあえず、そのまま出しましたが、どうせならipadにも対応したレスポンシブ設計にすべきだったかなと思います。

ちなみにAndroidではタブレットのリリースを切る設定は簡単なものはないようです。

Other

textタグはラッパーして使ったほうがいい

OSに設定されている文字サイズによって、フォントサイズ が変化するため、その設定の影響を受けないような設定が必要。
全てのtextタグに属性をつけるのは面倒なので、textタグはラッパーして使ったほうが使いやすい

こちらAccessibility (A11y) Walk-throughsに詳しい説明があります。

recoilの設計は慎重に

今回は、state管理にrecoilを使いました。
recoil自体は使いやすいものでおすすめできるものですが、ストレージの保存に用いる際は少し注意が必要でした。

実際のコードはこんな感じ、
editableState(変更可能)がtrueの場合にのみ、supplisをストレージに保存するという処理です。
supplisState(サプリの情報を格納)に、画面側で入力されたサプリ情報が格納されるわけですが、アプリを開いた時にもこのstate更新されるため、そのタイミングでeditableStateを適切に切り替える必要がでで、調整が大変しでした。

(アプリの仕様上、当日以外のデータは変更できないようになっています。)

export const supplisState = atom<Suppli[]>({
  key: 'supplsState',
  default: [],
});

export const editableState = atom<boolean>({
  key: 'editableState',
  default: true,
});

export const isSaveSupplisState = selector({
  key: 'isSaveSupplisState',
  get: ({get}) => {
    const supplis = get(supplisState);
    const editable = get(editableState);
    if (editable) {
      storageSave('mySuppli', supplis);
      return true;
    }
    return false;
  },
});

今考えると、supplisStateの役割を、

  • supplisInitState(初期値を呼び出し、state hookで十分そう)
  • supplisEditableState(編集○ -> supplisInitStateの値を移す)
  • supplisReadOnlyState(編集✖️ -> supplisInitStateの値を移す)

に分たほうが、ロジックもわかりやすかったかなと思います。
recoilのselectorには1つのatomのみを使うほうが後々楽そうです。

キーボード表示のスクロール制御がむずい

フォーム画面でreact-hook-formを使いましたが、このライブラリとmodalを組み合わせ他のが失敗でした。
今回、TextInputタグをタップしたときにmodalを表示し、タップしたbutton要素によって、自動で入力値を決定するような仕様にしました。

コードはこんな感じ、
Keyboard.dismiss();でキーボードが表示された瞬間に隠すということをしています😅
これ自体は個人開発では目をつぶれそうなものでしたが、modalを表示させると、そっちに全画面の高さが持っていかれるせいか、modalが消えた瞬間に自動的に画面が一番上にスクロールされちゃっています。

      <Controller
        control={control}
        render={({onChange, onBlur, value}) => (
          <>
            <TextInput
              style={[
                styles.input,
                shadowStyles('black').boxShadow,
                errors[`${index}nutrientName`] ? styles.invalid : styles.valid,
              ]}
              onBlur={onBlur}
              onChangeText={(v) => onChange(v)}
              value={value}
              placeholder="栄養素名"
              placeholderTextColor="lightgray"
              editable={formType === 'water' && index === 0 ? false : editable}
              onFocus={() => {
                Keyboard.dismiss();
                setIsScroll(false);
                setModalVisible(true);
              }}
            />
            <NutrientNameModal
              modalVisible={modalVisible}
              setModalVisible={setModalVisible}
              nutrientNameValue={value}
              nutrientNameOnChange={onChange}
            />
          </>
        )}
        name={`${index}nutrientName`}
        rules={{required: true}}
        defaultValue={nutrient ? nutrient.nutrientName : ''}
      />

自動スクロールはよくスマホアプリみる機能ですが、難しいことをしようとすると意外と調整が難しく、現状、勝手によくわからない自動スクロールがある状態でリリースしています。

最後に

個人開発でガッツリ開発してみると学びが多く、いいものですね〜✌️



ここまで読んでくれてありがとうございます☺️
記事の内容を読んでアプリの機能には興味がなくてもどんな感じなのか気になるかたのインストールも大歓迎です!!

iOS: https://apps.apple.com/jp/app/silent-flame/id1556855885
Android: https://play.google.com/store/apps/details?id=com.silentflame
使い方: https://note.com/esuke/n/n9f64c4d6c112