VBAとGASしか触ったことがなかったプログラミング初心者がReact Native(Expo)で初めてAndroidアプリを開発し、Google Playで初リリースするまでの試行錯誤の雑多な記録です。
実装時のメモ書きを記事化しているため、羅列的で読みにくい状態ですが、徐々に(思い出しながら)記録として整えていきたいと思います。
開発環境
- OS:Windows11 Home
- フレームワーク:React Native(Expo)
- 実行環境:Node.js
- 開発エディタ:VSCode
- リリース元:Google Play(Androidスマホアプリ)
開発期間
2024年1月ごろ~2024年10月12日に最初のバージョンをリリース
アプリ概要
- Googleの「連絡先」からピックアップ表示する電話帳アプリ
- アプリ上部のタブでグループ分けし、下部に電話帳がリスト表示される
- 電話番号ごとにTELアイコンがあり、タップすると端末の電話アプリに番号を渡して表示
開発環境の構築
- Node.jpのインストール(https://nodejs.org)
- Expoプロジェクトの作成
npx create-expo-app@latest
- テスト環境の構築(スマホにPlayからExpo Goをインストールする)
参照記事
プログラムの実装
*基本画面・画面遷移・上部タブグループ
アプリの画面遷移と上部タブグループの実装にド定番のReact Navigationを導入しました。ドキュメントが充実しているので初心者でも使いやすいです。
- react-navigation/native
- react-native-screens
- react-native-safe-area-context
- native-stack
- material-top-tabs react-native-tab-view
以下のコマンドで各ライブラリをインストールします。
npm install @react-navigation/native
npm install react-native-screens react-native-safe-area-context
npm install @react-navigation/native-stack
npm install @react-navigation/material-top-tabs react-native-tab-view
参照記事
実装については下記を参考にしました。
所感&次回実装ポイント
- 初回起動時にHomeタブにすべての連絡先が表示され、ユーザーの設定で各タブグループへ移動できるようにする
- タブラベルをユーザーが自由に設定できるようにする
- 上記の各設定画面を表示するため、ヘッダを付けて︙マークメニューを実装する
*ヘッダにタブグループをネストする
このアプリではヘッダ部分の下にタブグループを表示するため、下記のサイトを参照してスタックナビゲーターでタブグループをネストしました。
参照記事
所感&次回実装ポイント
-
<NavigationContainer>
は一箇所しか使えない
ネストで中に入るタブ表示の方のを外し、スタックの方にだけ入れるとうまくできた。 - ヘッダの表示ができたので、次はヘッダ右側に︙マークメニューを置く
*Google連絡先の読込(expo-contacts)
Android端末に搭載されているGoogle連絡先から電話番号のある連絡先を抽出するため、expo-contactsを導入しました。というか、もともと最初にExpoのドキュメントでexpo-contactsからプログラミングの学習を始めたので、そのままアプリ作成に突入したというのが実際です。
参照記事
所感&次回実装ポイント
- 連絡先データを表示するためには、
const [contacts, setContacts] = useState([])
で変数contactsにセットする。
*連絡先リストの表示
連絡先を一覧表示するためのライブラリとして、最初は実装が簡単なReact Native ElementsのListItemを使おうとしていたのですが、途中で表示に対するこだわりが発動し、結局はReact NativeのAPIのド定番であるフラットリストで実装しました。
を使ってコツコツとスタイリングするのは面倒ですが、デザインの細かな調整ができたのは良かったです。
結果的に電話番号が2つある場合は上下に分け、間に線を引き、電話をかけるアイコンもそれぞれ表示できるようになりました。見た目はほぼ満足できる状態に。
初心者なので、最初はわけもわからずなんでも簡単にできるライブラリを導入していました。そういうものは簡単なコードできれいに実現できるのは確かですが、細かい部分のカスタマイズ性には劣るので、使用する部分と使用しない部分は使い分けていくことになるのだなとわかってきました。
所感&次回実装ポイント
- フラットリスト形式で連絡先リストの名前が表示できたが、電話番号が出ない。Stack Overflowの下記の記事で書き方がわかった。
- 電話番号の横にタップして電話アプリを起動するためのTELアイコンを実装する
参照記事
*TELアイコンの表示
React Native ElementsからAvatarの導入
リスト内で絵柄が複数のTELアイコンを表示するのに、上記のライブラリから「Avatar」を導入しました。
参照記事
所感&次回実装ポイント
- 電話番号がある連絡先だけを表示するようにデータにフィルターをかける
*ヘッダメニュー
①ドロワーメニューの実装
ヘッダに︙マークを置いてそこから設定メニューを開くことにしましたが、ドロワーメニューも試してみることにしました。
まずは初歩的なメニューを作成してみます。下記サイトの指示どうりにライブラリをインストールします。
参照記事
npm install @react-navigation/drawer
npx expo install react-native-gesture-handler react-native-reanimated
下記を導入しようとしてみたけど、挫折。
試行錯誤の結果、簡単な普通の左メニューを実装しました。
さらにその後、またあれこれと試行錯誤し、やはりヘッダー右上に3点ドットアイコンを置き、タップすると選択メニューが開き、そこから「設定」画面やリストにチェックボックスを表示して個別選択しタブに移動できるようにすることにしました。
あれこれやって少し様子がわかってきたこともあり、結局はreact-native-popup-menuというライブラリを再度導入することにしました。
②react-native-popup-menuの実装
アイコンからメニューを表示させ、メニューのスタイルを整えるまでは簡単にできたのですが、メニュー内容をタップして別の画面(設定)に飛ぶ部分が難航。
react-native-popup-menuに関する試行錯誤の記録
画面間の移動はこの本元(基本!)にあるように書くだけだけど、
https://reactnavigation.org/docs/navigating
Expo Goで走らせると「cannot read property 'navigate' of undefined」と言われてエラーになってしまう。
スタック1でヘッダを表示し、headerRightに3点マークアイコンを置いてreact-netive-popup-menuでメニューを表示し、メニュー選択でスクリーン2のモーダル(ラベル設定)を表示するところまでは上手くいったが、スタック2に置いたテキストインプットの値を別のスクリーンに持っていくという機能がどうしても実現できない。
間にreact-netive-popup-menuが入っているためか、通常のnavigation.navigateでは画面遷移ができず、useNavigationContainerRefを使って遷移しているのが原因かと思うが、ネットに書いてある値の渡し方をいろいろ試したがまったくできなかった。
下記の記述のようにuseNavigationContainerRefを導入するとうまく行くことがわかった。大事なことは全部本家に書いてあるけど、自分がわかってないと見つけられないということ……
https://reactnavigation.org/docs/navigation-container/
結局、最終的にreact-netive-popup-menuを使うのをやめ、新たにスクリーン2にヘッダーメニューコンポーネントを作成し、ヘッダーアイコンを押すと透明モーダルでそれを表示し、その中のメニュー選択でスクリーン3に遷移するというナビゲーションにした。
この変更で通常のnavigation.navigateが使えるようになった。
③React Navigationでのネストスクリーンの実装
結局は下記を参考に、これまた手作業でコツコツとヘッダメニューの大枠を構築しました。
所感&次回実装ポイント
- ヘッダメニューの中身として下記の機能を入れる
・タブラベル変更
・チェックボックスで選択した連絡先を別のタブに入れる
・ホームタブを表示/非表示
・連絡先アプリを開く
・タブを増やす(オプション)
・テーマカラー(オプション)
*タブラベル変更メニュー
このあたりからCopilotを使用してコーディングするようになりました。試行錯誤の速度が早くなります。
実装内容
- モーダルスクリーン作成
- テキストインプットを配置
- Contextの導入とプロバイダの作成(ステートの全体化)
*チェックボックス表示とタブ選択用ヘッダの構築
連絡先を他のタブに移動させるため、メニュー選択で連絡先ごとにチェックボックスを表示する機能を実装します。
ユーザーがヘッダメニューから「他のタブに移動」を選択するとチェックボックス表示と同時に移動先のタブを選択するドロップダウンメニューがヘッダに表示されるようにします。
実装内容
- React Native Elementでチェックボックスを導入
- 連絡先データの取得、レンダーアイテム設定、フラットリストの各コンポーネントを分離
- 最上位のヘッダーを入れ替えるためのスクリーンを作成
- レンダーアイテムにチェックボックスを表示するように修正
- ヘッダーメニューで選択すると上記2つを同時に行い、表示を変える
- データにチェックの真偽を記入する項目を追加
所感&次回実装ポイント
- タブラベル編集と他のタブに移動の選択タブ(ピッカーリスト)を連動させ、ドロップダウンでユーザーが編集したタブラベルの名称を選択できるようにする。
*アプリの永続化(ストレージ)
ユーザーが他のタブに連絡先を移動したら、次回の起動時にその情報を読み込む必要があります。そこでアプリ固有の情報を端末に記録するためのライブラリを検討しました。
以前はReact NativeにAsyncStorageというAPIがあったけど使えなくなっていたので、他のライブラリを探しました。
React Native Async Storage、react-native-storage、react-native-mmkvなど、いろいろ検討しインストールもしてみましたが、結局はExpoで使えるReact Native Async Storageに決定しました。
実装内容
- データ作成部分を大幅修正し、連絡先からのデータ取得、ストレージ保存、各タブ用のリストを定義し取得や保存(新しい連絡先を使用しながらどのタブに入っているかの情報と合わせる)。
- チェックボックスをタップしたときの挙動を整理。タップ後のリストの再レンダリングしてチェック済が表示されるようにホームリストを設定。
*パッケージのバージョン管理
このあたりでExpo Goを走らせると依存関係がどうとか言われるようになったので、ここでいったんライブラリのバージョンを上げる作業をしました。
また、試行錯誤の過程でゴチャゴチャしてしまったので、app.jsonを確認し、不要なライブラリは削除しました。
npm outdated
とnpm list --depth=0
参照記事
*チェックしたリストのタブ移動の処理
Copilotに聞きながら少しずつコードを修正していきます。タブグループのタブ名はユーザーが変更できるので、任意のタブ名と各タブスクリーンの対応をどうするかの部分はインデックス番号で対応します。移動先のタブにすでにリストが存在する場合はデータの追加となるように修正します。
*描画の最適化
このあたりで描画がめちゃくちゃ遅いという問題に対応しました。useCallbackとuseMemoとmemoで解決しました。(記録が雑になってて細かい情報がなくてすみません。)
*カラーテーマ
ユーザーがアプリの色付きの部分の色を変更できる機能を実装しました。ここも記録メモがなく、後で思い出しながら追加します。すみません。
*タブデザインのカスタマイズ
タブのデザインに対するこだわりが発動し、tabBar関数を使って自分でカスタマイズしました。デザイン要素に加え、長いタブ名が付いても画面をスライドしてタブを選択できるようにしたり、文字数に応じてタブの横幅を可変にしたりと、使いやすさと見栄えの両方を重視しました。技術的な点については思い出したら追加します。
アプリのだいたいの機能が実装できましたが、せっかくなので課金システムも導入することにしました。Expoで推奨されているRevenueCatというサービスを使うことにしました。このライブラリはExpo Goに非対応のため、また、課金はGoogleのシステムも関係するため、ここから先はGoogle Play Consoleでのテストに移行することにしました。
開発ビルド
アプリのテストバージョンをGoogle Play Consoleに登録するため、Expoのビルド機能を利用して実行ファイルを作成します。
その前にRevenueCatの機能を使うためのライブラリも導入しておきます。簡単に実装するため、画面構成までしてくれるRevenueCatのreact-native-purchases-uiも導入しました。
ところが、何回もビルドエラーが起こり、実行ファイルが作成できません。最終的にreact-native-purchases-uiがビルドエラーの原因だとわかり、ライブラリをアンインストールしたらビルド可能になりました。
そのため、課金システムの画面も実装する必要が出てきました。
*ステータスバーの背景色の処理
次に、開発ビルドだとステータスバー背景色が変更できないという問題に直面。expo-statusbarに変えてuseEffectでsetStatusBarBackgroundColor処理したうえ、各スクリーン毎にstatusBarColor:themeColorを入れて解決しました。
参照記事
*画像のエラー
描画自体に問題は無いものの、「ReactImageView: Image source "null" doesn't exist」という警告が消えないので調べます。
下記を参照しreact nativeのImageからexpoのImageに変更しましたが、問題が起こることに変わりません。
さらにエラー文で検索した結果、下記の記事を発見。これが原因で、書かれているように対処するとエラーが消えました。
*RevenueCat(課金システム)
こちらについてはいろいろあったので、別記事にまとめようと思います。
*アプリアイコンの表示問題
Google Play Consoleでのテストをしている間にアプリのアイコンデザインを変更したのですが、インストールしたアプリに反映されません。
Expoの公式に沿ってassetフォルダに各種アイコンを格納してapp.jsonも修正していたのになんで変わらないんだろうと思っていたのですが、下記の記事でやっと原因がわかりました。
アプリのアイコンの情報がビルド時にassetフォルダからandroidフォルダに登録される仕組みのようで、androidフォルダ内の画像が古いままだったことが原因でした。assetフォルダ内のアイコン画像の変更をビルドファイルに反映させるには再度プレビルドすることが必要でした。
ビルド時にexpoフォルダも自動生成され、そちらにもアイコン画像が作られているので、androidフォルダとexpoフォルダの両方を一度削除してプレビルドで再生成します。これでアプリアイコンの変更をインストールしたものに反映することができました。
参照記事
製品ビルド
RevenueCatのアイテム表示画面が完成したので、公式のドキュメントに沿ってアプリを製品ビルドしてGoogle Play Consoleでクローズドテストのリリースに登録します。最初は5日間くらいかかります。
問題があるとGoogleから直せという指示が来るので、テストしてエラーを直すこと3回目でクローズドテストに進むことができました。
その後も気になった部分や問題点を修正し、Google Play用の紹介画像なども作成・登録し、まったくなにもわからない状態から始めて10ヶ月後、始めてのアプリをリリースすることができました。