前書き
自分と子供が大好きな動物園でお題の動物を探してビンゴを目指すフィールドビンゴアプリ 「どうぶつビンゴ」 を開発中!
この記事では、アプリの作成過程や得た学び、躓いたことを書いていきます。
定期的に内容追加していく予定です!
目次
0. React Native & Expo とは?
1. 環境準備
2. React Native アプリのソースに触れる
3. アプリの公開
4. 個人的な学び
5. 躓きポイント集
0. React Native & Expo とは?
はじめに、React Native と Expo を簡単に紹介します。
React Native とは?
- クロスプラットフォーム開発
- 1つのコードで iOS と Android の両方のプラットフォーム向けにネイティブなアプリを開発できる。
- JavaScriptベース
- React Native は JavaScript をベースにしており、React の構文を活用している。そのため、JavaScript や React の知識を活かして開発できる。
- ネイティブ機能へアクセス可能
- React Native はネイティブモジュールを組み込むことができるため、カメラ、位置情報、通知などのモバイルデバイスのネイティブ機能にアクセスすることができる。
- 再利用可能なコンポーネント
- React のようなコンポーネントベースのアーキテクチャが採用されているため、UI要素を再利用可能なコンポーネントとして構築することができる。
Expo とは?
- セットアップが簡単
- Expo は、React Native の開発環境のセットアップを簡単にできる。また、Expo CLI を使用することで、複雑な設定やビルド手順を最小限に抑えることができる。
- 即時にプレビュー可能
- Expo Client アプリをデバイスにインストールすることで、Expo プロジェクトをリアルタイムでプレビューできる。実際のモバイルデバイスでアプリの見た目や機能を確認できるため、開発プロセスがスムーズになる。
- Expo SDKの提供
- 多くの便利なSDKが提供されている(カメラ、位置情報、通知、センサーなどのデバイス機能に簡単にアクセスできる)。これにより、ネイティブ機能を利用したアプリを簡単に開発できる。
- 豊富なExpoライブラリを持つ
- UIコンポーネントやAPIラッパーなど多くの便利なライブラリやモジュールが提供されている。
1. 環境準備
ここでは、Expo を使用した React Native アプリを作成する準備をしていきます。
React Native & Expo でプロジェクト作成
前提として、Node.js および NPM が端末にインストール済みであることとします。
未インストールの場合、このサイトが参考になるかと思います。
あとは好みですが、開発する上で VScode も準備があると便利かもしれません
① React Native プロジェクトを Expo で作成する
// Expo-CLI をインストールする
npm install -g expo-cli
// expo プロジェクトの初期設定
expo init your-project-name
your-project-nameの箇所はプロジェクトのフォルダ名になる(アプリ名にもなる)ため、各自で置き換えてください(あとでも編集可能)。
② プロジェクトのあるディレクトリへ移動する
cd your-project-name
③ プロジェクトを起動する
expo start
処理が完了すると、QRコードがターミナルに表示にされます。
このコードをスマホで読み取り、次に説明するExpo Go アプリで起動させると、実機確認が行えます!
Expo推奨のプロジェクト作成方法は、npx create-expo-app your-project-name
だそうです。
また、expo-cli
のインストールせずに、npx
を頭につけて実行すること
も推奨しています(公式のブログ 参照)。
上記を取り入れたプロジェクト作成手順が以下になります
① React Native プロジェクトを Expo で作成
npx create-expo-app your-project-name
② プロジェクトのあるディレクトリへ移動
cd your-project-name
③ プロジェクト起動
npx expo start
Expo Go の導入
npx expo start
した際に表示されたQRコードを使って実機確認を行うためには、Expo Go というExpo 公式アプリ(無料)をスマホにインストールする必要があります。
iPhoneでは、App Store(Androidなら、Google Playストア)で、Expo Go
と検索し、インストールします。
インストールが完了したら、スマホのカメラを起動させ、先ほどプロジェクトを起動したときに表示されたQRコードを読み込んで、Expo Go で開いてください。
「Open up App.js to start working on your app!」という表示がされればOKです。
プロジェクトを立ち上げたローカル端末とExpo Go をインストールしたデバイスが同一ネットワーク上(お互いを検出できる状態)に存在していないとExpo Goで資材を起動できません
※自分はこれで躓きました…(躓きポイント集_Expo Go の立ち上げに失敗 にて、詳細を記載してます)。
また、今後使用するため、Expoアカウントも作成しておきましょう。
Expoアカウント作成方法は、こちらの記事が参考になりました。
2. React Native アプリのソースに触れる
ここでは、React Nativeのソースについての説明や実際にコードに触れていきます。
代表的なファイル・フォルダの説明
はじめに、デフォルトのソースに存在するフォルダ・ファイルの中で、よく使われるものについて簡単に説明します。
① App.js / index.js
App.js または index.js は、通常、React Native アプリのエントリーポイントとなるファイル。ここからアプリケーションが開始される。主に、ルートコンポーネント(NavigationContainerなど)が定義され、アプリケーション全体の構成が行われることが多い。
② Assets フォルダ
Assets フォルダは、アプリで使用する画像・アイコン・フォントなどの静的なリソースを配置・管理するためのフォルダ。
③ Components フォルダ
Components フォルダには、作成したUIコンポーネントが含まれる。
例えば、ボタン、ヘッダー、フォームなどのコンポーネントを別々のファイルに分割・本フォルダ内管理し、必要な時に使いまわせるようにする。
④ Screens フォルダ
Screens フォルダには、アプリ内の各画面に対応するコンポーネントが含まれる。
例えば、ホーム画面、詳細画面、設定画面などのスクリーンを個別のファイルとして作成し、本フォルダで管理する。
⑤ app.json / package.json
app.json は、アプリの設定を保持するファイルで、アプリの名前・バージョン・アイコンなどの情報を定義している。一方、 package.json は、アプリケーションの依存関係・スクリプト・バージョン情報などを定義している。
⑥ node_modules
node_modules フォルダは、プロジェクトで使用されている依存関係のパッケージがインストールされている。npm や yarn を使用してインストールしたライブラリやモジュールがここに含まれる(Gitなどでリモートリポジトリにpushする際には、フォルダごとignoreされることが多い)。
代表的なコンポーネントの説明
前述したように、React Nativeでは、コンポーネント組み合わせてモバイルアプリのUIを構築します。次は、代表的なReact Nativeのコンポーネントについて説明します。
① View コンポーネント
View は、UI を構築するための基本的なコンテナ(画面上の要素を配置するためのブロックやボックスのようなもの)。例えば、テキストやボタン、画像などの他のコンポーネントを含めるための領域を提供する。
HTMLで汎用的に使用するdiv
タグなどは一切使用できません。
import { View, Text, StyleSheet } from 'react-native';
const App = () => {
return (
<View style={styles.container}>
<Text>Hello, React Native!</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
export default App;
② Text コンポーネント
Text は、表示するテキストを表すコンポーネント。通常、View 内に配置され、ユーザーに表示する文章やラベルなどのテキスト情報を提供する。
HTMLでテキスト情報に使用するp
タグなどは一切使用できません。
import { View, Text, StyleSheet } from 'react-native';
const App = () => {
return (
<View style={styles.container}>
<Text>Hello, React Native!</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
export default App;
③ Image コンポーネント
Image は、画像を表示するためのコンポーネント。URL からの画像読み込みやローカルの画像ファイルの表示が可能。
※個人的に画像表示で躓いた箇所があったため、躓きポイント集_画像表示できない にて、詳細を記載してます。
import { View, Image, StyleSheet } from 'react-native';
const App = () => {
return (
<View style={styles.container}>
<Image
source={{
uri: 'https://example.com/image.jpg',
}}
style={{ width: 200, height: 200 }}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
export default App;
④ Button コンポーネント
Button は、ユーザーがタップできるボタンを表示するためのコンポーネント。タップされたときに特定のアクションをトリガーすることができる。
シンプルで直感的なボタンを作成できる反面、ボタンのスタイリングの柔軟性に欠ける。
import { View, Button, Alert, StyleSheet } from 'react-native';
const App = () => {
const showAlert = () => {
Alert.alert('Button Pressed!');
};
return (
<View style={styles.container}>
<Button title="Press Me" onPress={showAlert} />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
export default App;
要件次第ではボタンの機能を残しつつ、ボタンのスタイリングを柔軟に行いたいことがあると思います。その場合、Pressable コンポーネントを利用すると良いです。
import { Pressable, Text, Alert, StyleSheet } from 'react-native';
const App = () => {
const handlePress = () => {
Alert.alert('Button Pressed!');
};
return (
<Pressable onPress={handlePress} style={styles.pressable}>
<Text>Press Me</Text>
</Pressable>
);
};
const styles = StyleSheet.create({
pressable: {
padding: 10,
backgroundColor: 'lightblue',
borderRadius: 5,
},
});
export default App;
React Native には、他にもたくさんのコンポーネントが用意されています(公式参照)。
各種ライブラリを使えば、自作しなくてもいろいろなUIを組めるので、必要に応じて探してみて下さい!
コードに触れる
説明が長くなりました🙇
いよいよコードを触っていきましょう!
① Text コンポーネントを変更し、画面表示を変更してみる
App.jsを開き、<Text></Text>
の中身を好きな文字で書き換えましょう。
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
export default function App() {
return (
<View style={styles.container}>
<Text>はじめての、React Nativeアプリ制作!!</Text>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
保存すると、Expo Go アプリの画面に変更された文言で表示されたかと思います!
(デフォルトでホットリロード機能が付いているため、即時に画面確認が行えてめっちゃ便利 )
② 画面遷移できるようにしてみる
せっかくなので、画面遷移なんかもできるようにしちゃいましょう!
はじめに、必要なReact Navigation
ライブラリと必要なライブラリをインストールします。
// @react-navigation/native インストール
npm install @react-navigation/native
// @react-navigation/native の必要ライブラリ
// Expo利用している場合
npx expo install react-native-screens react-native-safe-area-context
// Expo利用していない場合
npm install react-native-screens react-native-safe-area-context
// @react-navigation/stack インストール
npm install @react-navigation/stack
// @react-navigation/stack の必要ライブラリ
// Expo利用している場合
npx expo install react-native-gesture-handler
// Expo利用していない場合
npm install react-native-gesture-handler
インストールが完了したら、App.jsを以下のように改修します。
ここでは、ページ遷移を管理するための Navigation Container および Stack Navigator を作成し、アプリ全体で使用できるように設定しています。
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import HomeScreen from './screens/HomeScreen';
import DetailsScreen from './screens/DetailsScreen';
const Stack = createStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
次に、遷移元になるHome画面と遷移先になるDetails画面を作成していきます。
まず、上記2ファイルの格納先フォルダを作成します。
mkdir screen
フォルダができたら、それぞれのファイルを作成していきます。
import { View, Text, Button } from 'react-native';
const HomeScreen = ({ navigation }) => {
return (
<View>
<Text>ここは、Home画面です</Text>
<Button
title="Details画面へ"
onPress={() => navigation.navigate('Details')}
/>
</View>
);
};
export default HomeScreen;
navigation.navigate()
を使用することで、Stack Navigator内の他の画面へ遷移できるようになります。
Home画面では、Details画面への遷移のために、ButtonコンポーネントのonPress
にnavigation.navigate()
を設定しています。
import { View, Text, Button } from 'react-native';
const DetailsScreen = ({ navigation }) => {
return (
<View>
<Text>ここは、Details画面です</Text>
<Button title="戻る" onPress={() => navigation.goBack()} />
</View>
);
};
export default DetailsScreen;
この状態でExpo Goの画面を確認すると、Home画面が表示され、「Details画面へ」ボタンを押すとDetails画面へ遷移できます。
また、Details画面の「戻る」ボタンを押すと、Home画面へ戻れます(goBack()
を使用することで、ひとつ前のページへ戻れる)。
このようにコンポーネントやライブラリを組み合わせながら、自分なりのアプリ作成を楽しんでみてください!
3. アプリの公開
作成したアプリを App Store へ公開しましょう!
Apple Developer Program への登録
App Store へアプリを公開するためには、Apple Developer Program(Appleが開発者へ提供している、iOS や macOS 向けのアプリやソフトウェアを開発・App Store に公開するための機能やリソースにアクセスできるプログラム)への登録(有料、1年ごとの更新制)が必要になります。
こちらのサイトの手順が参考になるかと思います!
※最後の決済部分で自分は躓いたため、躓きポイント集_Apple Developer Program に登録できない に詳細を記載してます。
リリース資材の準備
アプリをリリースするには、App Store Connect にて様々な設定等を行う必要があります。
App Store Connect とは?
- アプリ開発者が App Store でアプリを管理し、配信するためのオンラインプラットフォーム。
- 開発者はこのプラットフォームを使用して、アプリの提出、管理、分析、収益化などを行うことができる。
ipaファイルの作成
ipaファイル(iOSアプリを配布するためのファイル形式)作成の前段階として、コードの他に設定ファイルと画像ファイル(アイコン画像やスプラッシュ画像)が必要になります。
- アイコン画像は、1024*1024が推奨サイズ
- スプラッシュ画像は、以下のガイドライン推奨
- iPhone 8までの機種
- Portrait(縦向き): 750px × 1334px
- Landscape(横向き): 1334px × 750px
- iPhone 8 PlusやiPhone X以降の機種
- Portrait(縦向き): 1242px × 2208px
- Landscape(横向き): 2208px × 1242px
- iPad
- Portrait(縦向き): 1536px × 2048px
- Landscape(横向き): 2048px × 1536px
- iPhone 8までの機種
-
app.json
にアプリ名やバンドルID、アイコンなどを設定
※expo.ios
のbundleIdentifier
は独自のものを設定
{
"expo": {
"name": "YourAppName",
"slug": "your-app-slug",
"privacy": "public",
"platforms": ["ios"],
"version": "1.0.0",
"orientation": "portrait",
"icon": "./path/to/your/app-icon.png",
"ios": {
"bundleIdentifier": "com.your.app.identifier",
"buildNumber": "1.0.0",
"supportsTablet": false
},
"android": {
// Android向けの設定
},
// その他の設定
}
}
ここから、ipaファイルを出力していきます。
以前は、expo build:ios
を実行することで ipaファイルを作成できたが、執筆した2024年1月時点では、eas build -p ios
で実行する必要あり(Expo公式参照)。
準備として、EAS-CLIをインストールします。
npm install -g eas-cli
次に、Expoアカウントを使用してEASアカウントにログインします。
eas login
ログイン後、自身のプロジェクト直下にてビルド実行します。
cd your-project-name
eas build -p ios
Apple ID&パスワードを求められるため、事前に準備しておきましょう。
上記コマンドを実行し、問題なく完了すると以下のような画面が出ます。
https://expo.dev/artifacts/eas/xxxxxxxxxx.ipa
のリンクを押すと作成されたipaファイルをPCへダウンロードできます。
eas build
は、 Expoの無料プランだと30回/月(iOS用ビルド:15回/月、Android用ビルド:15回/月)まで無料でできます。
無料の範囲を超えると、実施回数ごとに課金される。
App Store Connect でアプリ情報登録
基本的には、こちらのサイトを参考にしました。
アプリ情報登録の中で、アプリ資材であるipaファイルをApp Store Connectへアップロードする必要があるのですが、基本的にWindows端末からアップロードできないです。
なんとか解決方法を探した結果、App Uploader なるツールを使ってアップロードしました(初めて使用するApple ID登録してから無料の試用期間7日間で、その後購入も可能)。
Macある方は、XcodeやTransporterで簡単にApp Store Connectへipaファイルをアップロードできます!
(一度目のリリースの後、便利さを求めてWindowsからMacに乗り換えてしまいました…。ipaファイル上げ放題、iOSシミュレータも使い勝手抜群。)
問合せ先やプライバシーポリシーサイトも必須になります。
問合せ先は、XアカウントでもOKみたいです。
プライバシーポリシーサイトは、プライバシーポリシーが記載されている静的サイトがあればOKみたいです(自分の場合は、Next.js + Vercelで作成しました)。
審査通過後のリリース方法も選択できます(自動的にリリースするか、自分のタイミングで手動リリースするか)。
すべての情報を登録したら、Appleへ審査提出し、結果を待ちます
最後に選択したリリース方法でリリースされることで、App Storeにアプリが公開されます!!
4. 個人的な学び
ChatGPTとビンゴロジック作成
今回のアプリ作成の中で、ビンゴロジックをChatGPTと一緒に作ってみました。
ChatGPTへ指示していて、思った通りの判定条件にならないことが多く大変だった。
縦・横・斜めそれぞれで、初めてビンゴになったときのみ「ビンゴ!」と表示させたいという要件があったため、以下のようなやり取りをしていきました。
-
「ビンゴになったとき、「ビンゴ」と表示して」と依頼すると、縦・横・斜めのいずれかで一度でもビンゴになると、それ以降は常にどこかのマスを押下すると「ビンゴ」という表示が出てしまうロジックになってしまった
-
上記1の事象を修正するために、ロジックの正しい動き方を説明した。
a. 「各列、各行、主対角線、副対角線で新しくビンゴが達成される度に、「ビンゴ!」表示をしたい」と説明
⇒ まだビンゴどこかがビンゴの状態で別のマス押すと、ビンゴになっていなくてもビンゴ判定にb. 「ただし、各列、各行、主対角線、副対角線でビンゴが既に達成されている場合は、「ビンゴ!」表示をさせない」と説明
⇒ 各列、各行、主対角線、副対角線でのビンゴを区別&前回のビンゴ状態と比較する方向にコードが変わったが、まだダメc. コードに判定条件の漏れがあったことや提案されたコードでは動作が不十分の旨を指摘するやりとりを数回実施
⇒ あと少しで要件満たせそうな感触を得始める!d. 「各列、各行、主対角線、副対角線のそれぞれのビンゴ判定を行い、その後、前回との差分確認し、新規のビンゴがあった場合のみ、「ビンゴ」を表示させる必要がある」と説明
⇒ ついに、新規のビンゴ時のみ「ビンゴ」という表示が出るように!!!(縦・横・斜めのいずれかがビンゴになっている状態で別のマスを押下すると、新規のビンゴ時に限り、ビンゴ判定となるようになった!)
少し複雑なコードをChatGPTに書いてもらうには、丁寧な説明だったり、TRY&ERRORのやり取りが必要との個人的な学びを得ました。
5. 躓きポイント集
個人的に躓いたところを…。
Expo Go の立ち上げに失敗
- 同一のWi-Fiを使用してローカル起動&Expo Go起動したが、Expo Goでアプリの起動ができない事象が発生。
結論:PCとスマホが同一ネットワーク上との認識がされていなかった
- 同一Wi-Fiで接続していたが、PCのネットワークがパブリックネットワークになっていたため、Expo Goとのネットワークがつながっていなかった。
- このサイトを参考に、ネットワークをパブリックからプライベートに変えたら接続できた。Microsoftの公式見てもパブリック⇔プライベートの変更方法解決しなかったので大変助かりました(切り替えのラジオボタンなくてコマンドで変更できた)。
画像表示できない
- ビンゴカードの各マスに画像表示させる際に、画像表示ができない事象が発生。
以下、コード(抜粋)
export const bingoData = [
{ name: 'ライオン', marked: false, imgPath: './assets/lion.png' },
{ name: 'トラ', marked: false, imgPath: './assets/tiger.png' },
...
]
function BingoCard({ bingoCardData, onCellPress }) {
return (
<View style={styles.card}>
{bingoCardData.map((row, rowIndex) => (
<View key={rowIndex} style={styles.row}>
{row.map((cellData, colIndex) => (
<Pressable
key={colIndex}
style={[styles.cell, cellData.marked ? styles.cellMarked : null]}
onPress={() => onCellPress(cellData.name)}
>
<Image style={styles.image} source={require(cellData.imgPath) resizeMode={'contain'} } />
</Pressable>
))}
</View>
))}
</View>
);
}
error: BingoCard.js: BingoCard.js:Invalid call at line 14: require(cellData.imgPath)
ERROR [Error: TransformError BingoCard.js: BingoCard.js:Invalid call at line 14: require(cellData.imgPath)]
Error: BingoCard.js:Invalid call at line 14: require(cellData.imgPath)
at transformJS (C:\Users\username\bingo-app\node_modules\metro-transform-worker\src\index.js:225:15)
at transformJSWithBabel (C:\Users\username\bingo-app\node_modules\metro-transform-worker\src\index.js:343:16)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Object.transform (C:\Users\username\bingo-app\node_modules\metro-transform-worker\src\index.js:461:12)
Error: BingoCard.js:Invalid call at line 14: require(cellData.imgPath)
結論:表示させるデータで require する
- コンソールログで確認すると、パスとなる文字列は指定できていたが、うまく表示ができなかった(
source
プロパティはnumber
型しか受け取らないが、実際はstring
型を渡そうとしていた)。 - ビンゴカード側で
require
していたが、表示させるデータでrequire
することで無事画像表示できた(おそらく改修の余地はまだありそうだが、動くものベースで一旦OKとした)。
以下、コード(抜粋)
export const bingoData = [
{ name: 'ライオン', marked: false, uri: require('./assets/lion.png') },
{ name: 'トラ', marked: false, uri: require('./assets/tiger.png') },
...
]
function BingoCard({ bingoCardData, onCellPress }) {
return (
<View style={styles.card}>
{bingoCardData.map((row, rowIndex) => (
<View key={rowIndex} style={styles.row}>
{row.map((cellData, colIndex) => (
<Pressable
key={colIndex}
style={[styles.cell, cellData.marked ? styles.cellMarked : null]}
onPress={() => onCellPress(cellData.name)}
>
<Image style={styles.image} source={cellData.uri} resizeMode={'contain'} />
</Pressable>
))}
</View>
))}
</View>
);
}
Apple Developer Program に登録できない
- Webブラウザから、Apple Developer Program にアカウント登録して、いざ年間のサブスクを決済したところ、翌日勝手にキャンセルされてしまっていた。
- その後再度決済しようとしたところ、決済ができなくなってしまった。
- 自分のクレジットカード(複数ブランド)で試したが全滅。
- クレカ会社に問合せしたが、決済OKにはなっていたとのこと
結論:App Store でダウンロードした Apple Developer App から登録できた
- Appleにメールで問合せしたところ、App Store から Apple Developer App をダウンロードして、そこから登録するようにと促された。
※電話問合せだと、「理由は開示できませんが、登録できませんでした」と言われて終わりなので、メールで問合せした方がよいです。 - 案内に従って登録進めたところ、無事できた。
最後までご覧いただき、ありがとうございました