目的
Expoで作成したReactNativeアプリケーションからローカルで起動したバックエンドに接続する。
今回はサンプルのレポジトリを載せているので関係ある部分だけ触れる。
実機(今回はiPhone)で動作確認する場合はlocalhostを使用できないのでこの点についてのメモ。
使用するバックエンド
KotlinのSpringBootを使用して接続確認用のエンドポイントを作成。
接続成功するとmessageが入ったjsonを返す。
ローカルホストの3000ポートで起動させる。
Spring Bootのエンドポイント
@CrossOrigin("http://localhost:8081")
@RestController
class DemoController {
@GetMapping("/api/demo")
fun demo(): DemoResponse = DemoResponse(
message = "Success call API!"
)
}
@CrossOrigin("http://localhost:8081")がないとCORSポリシーによってブロックされる。SpringSecurityを使用している場合はCorsConfigurationSourceクラスなどを使って別で設定できる。
期待する成果物
Check API Message.をクリックした後の画面(API呼び出し成功時)
api-message.tsx

実装(localhost:3000で固定の場合)
最初にExpo公式ドキュメントを参考にプロジェクトを作成して、リセットする。
初期画面は以下のソース
import {Button, Text, View} from "react-native";
import {useRouter} from "expo-router";
export default function Index() {
const router =useRouter();
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
}}
>
<Text>API接続チェック</Text>
<Button title="Check API Message." onPress={() => router.navigate('/api-message')} />
</View>
);
}
Check API Message.をクリックするとapi-message.tsxを表示する。
import {useEffect, useState} from "react";
import {Text, View} from "react-native";
export default function ApiMessage() {
const [message, setMessage] = useState<string>("");
useEffect(() => {
let ignore = false;
// マウント時に一度だけ API を叩く
const fetchMessage = async () => {
try {
const apiMessage = await getApiMessage();
setMessage(apiMessage);
} catch (e) {
console.error(e);
if (!ignore) {
setMessage("エラーが発生しました");
}
}
};
void fetchMessage();
// クリーンアップ関数
return () => {
ignore = true; // アンマウント時にフラグを立てる
};
}, []);
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
}}
>
<Text>{message}</Text>
</View>
);
}
async function getApiMessage(): Promise<string> {
// クエリパラメータの構築
let url = 'http://localhost:3000/api/demo';
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
console.log('Status:', response.status);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: { message: string } = await response.json();
return data.message;
}
こんな感じで一旦APIの呼び先をハードコーディングして動作確認してみる。
検証(localhost:3000で固定の場合)
- ブラウザ
- iPhone実機(expo startで表示されたQRコードを使用してExpoAppで起動)
ブラウザやMac上でのiPhoneのシミュレータでは問題なくAPIを呼び出すことができるが実機のシミュレータの場合は失敗する。バックエンドとは別端末となるのでlocalhostで失敗するのはまあ当たり前である...
解決策
前提:APIサーバーを起動している端末(PC)とシミュレータで使用するiPhoneは同じWi-Fi接続を使用する。
次のコマンドで現在使用しているネットワークインターフェースのIPアドレスを取得する。
(Windowsの場合は別のコマンド)
ipconfig getifaddr en0
この取得したIPアドレスを以降{IP}とする。
この値を環境変数に設定して使う。
今回は.env.localという開発環境の環境変数を管理する次のようなファイルをプロジェクトルートに作成する。
EXPO_PUBLIC_API_URL=http://{IP}:3000
.env.localについて
Expo公式ドキュメントを参考にCLIからプロジェクトを作成した場合.gitignoreにあらかじめ次のような行が追加されていてプッシュされないようになっている。
# local env files
.env*.local
実装(IPを設定する場合)
プロジェクトルートにsrcとconfigフォルダを作成して次のような環境変数にデフォルト値を持たせて管理するapi.tsを作成する。
他にデフォルト値を指定して環境変数を設定するようなパラメータがあればapi.tsに追加してまとめて管理する。
export const API_URL =
process.env.EXPO_PUBLIC_API_URL ?? "http://localhost:3000";
import {useEffect, useState} from "react";
import {Text, View} from "react-native";
import {API_URL} from "@/src/config/api";
//中略
async function getApiMessage(): Promise<string> {
// クエリパラメータの構築
let url = API_URL + '/api/demo';
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
console.log('Status:', response.status);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: { message: string } = await response.json();
return data.message;
}
のようにgetApiMessage()の変数urlを変更する。
検証(IPを設定する場合)
- ブラウザ
- iPhone実機(expo startで表示されたQRコードを使用してExpoAppで起動)
実機シミュレータでローカル実行中のバックエンドへのアクセスが確認できた。
サンプル
まとめ
ReactNativeの公式ドキュメント等に書かれていそうだけど私の探し方が良くないのか見つけられなかった。
参考
Expo Constantsの使用
kazutoyoさんがExpoドキュメントのConstantsを使用して取得できるとコメントを下さったので確認。ありがとうございます!
このように修正するとapiの呼び出しに成功する。
- import {API_URL} from "@/src/config/api";
+ import Constants from "expo-constants";
async function getApiMessage(): Promise<string> {
// クエリパラメータの構築
- let url = API_URL + '/api/demo';
+ const hostUri = Constants.expoConfig?.hostUri;
+ const host = hostUri ? hostUri.split(':')[0] : 'localhost';
+ const url = `http://${host}:3000/api/demo`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
console.log('Status:', response.status);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: { message: string } = await response.json();
return data.message;
}
ブレークポイントを設定してhostUriの中身を確認。8081ポートを使用しているためXXX.XXX.X.XXX:8081といった値が格納される。
変数名をhostとしてポート番号以外の部分を抽出してurlを組み立てる。
実際にはurlのhttp://やポート番号の部分は起動時のオプションなどで切り替わるように実装されると思う。
