Rorkで「近くのお店を探すアプリを作って」「ランニング記録アプリを作って」とお願いすると、AIは expo-location や react-native-geolocation を使ったコードを生成してくれます。動かしてみると一見動きそうなのに、いざ実機で試すと位置が取れない、許可ダイアログがそもそも表示されない、毎回タイムアウトで落ちる——こうした症状に出会った方は多いのではないでしょうか。私も位置情報を使うアプリを作ったときに、半日以上ハマって原因を切り分けたことがあります。
位置情報まわりは、コードが正しくてもInfo.plistやAndroidManifestの設定、シミュレータと実機の挙動差、OS側の権限設定のいずれかで詰まりがちな領域です。Rorkで生成したアプリで位置情報が取れないときに、上から順に確認していくと最短で原因にたどり着ける5つのポイントを実例とともに整理しました。
ポイント1: そもそも許可ダイアログが出ているかを切り分ける
最初に切り分けたいのは「許可ダイアログが出ているのに拒否されている」のか、「ダイアログが一度も出ていない」のかです。この2つは原因も対処も全く違います。
iOSなら「設定」→「アプリ名」→「位置情報」を開き、項目自体が表示されているか確認してください。項目自体が無い場合、アプリは一度も位置情報の許可をリクエストしていないということです。Androidの場合は「設定」→「アプリ」→該当アプリ→「権限」で同様に確認できます。
// 許可状態をログに出して確認するコード
import * as Location from 'expo-location';
async function checkLocationPermission() {
const { status, canAskAgain, granted } = await Location.getForegroundPermissionsAsync();
console.log('現在の状態:', { status, canAskAgain, granted });
// 期待出力例:
// { status: 'undetermined', canAskAgain: true, granted: false } → 未リクエスト
// { status: 'denied', canAskAgain: false, granted: false } → 拒否済み(再リクエスト不可)
// { status: 'granted', canAskAgain: true, granted: true } → 許可済み
}status が undetermined のまま requestForegroundPermissionsAsync() を呼んでいないなら、後述のポイント3に進みます。denied で canAskAgain: false の場合、コードからは再要求できないので「設定アプリへ誘導するボタン」を用意するしかありません。
ポイント2: app.jsonの設定漏れ(最も多い原因)
私の経験上、Rorkで生成された直後のプロジェクトで最も多い原因がこれです。expo-locationをインストールしただけでは権限の説明文が設定されておらず、特にiOSではビルドは通るのに許可ダイアログが一切表示されない、という症状になります。
app.json に以下のように expo-location のplugin設定を追加してください。
{
"expo": {
"plugins": [
[
"expo-location",
{
"locationAlwaysAndWhenInUsePermission": "ランニング記録のため、アプリ使用中とバックグラウンドで位置情報を使用します。",
"locationWhenInUsePermission": "近くのお店を表示するために位置情報を使用します。",
"isIosBackgroundLocationEnabled": false,
"isAndroidBackgroundLocationEnabled": false
}
]
]
}
}ここで肝心なのは、説明文をアプリの目的に合わせて具体的に書くことです。「位置情報を使用します」だけだとApp Storeの審査で「目的が不明確」としてリジェクトされる可能性があります。「ランニング記録のため」「近くの店舗検索のため」のように、ユーザーが許可してもよいと思える根拠を示してください。
設定を追加したら、Expo Goでは反映されません。npx expo prebuild --clean を実行してネイティブプロジェクトを再生成し、EAS Buildで実機向けビルドを作り直す必要があります。ここを忘れて「JS側を直したのに直らない」とハマるケースが本当に多いので注意してください。
ポイント3: 許可リクエストのタイミングが早すぎる
Rorkが生成するコードを見ていると、コンポーネントの一番上の useEffect で起動直後に位置情報を要求している実装をよく見かけます。これ自体は動きますが、ユーザー体験としてあまり良くなく、特にiOSでは「アプリを開いた瞬間に許可ダイアログが出る」と直感的に拒否されやすくなります。
// ❌ 起動直後にいきなり要求(ユーザーがびっくりして拒否しがち)
useEffect(() => {
(async () => {
await Location.requestForegroundPermissionsAsync();
})();
}, []);
// ✅ ユーザーが「現在地を使う」ボタンをタップした時点で要求
const handleUseCurrentLocation = async () => {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
Alert.alert(
'位置情報が必要です',
'現在地から検索するには、設定アプリで位置情報を許可してください。',
[
{ text: 'キャンセル', style: 'cancel' },
{ text: '設定を開く', onPress: () => Linking.openSettings() },
]
);
return;
}
const location = await Location.getCurrentPositionAsync({});
setLocation(location);
};ユーザーの行動と紐付けて要求することで、許可率が体感で2〜3倍変わります。私はこのパターンに切り替えてから、許可率が大きく改善した記憶があります。
ポイント4: シミュレータでは動くのに実機で動かないケース
シミュレータと実機の挙動差で詰まるパターンも多いので、原因別に整理しておきます。
- iOSシミュレータの位置情報がオフ: シミュレータのメニュー「Features」→「Location」が「None」になっていると、コードは正しくても座標が返ってきません。「Apple」や「City Run」を選ぶとダミーの位置が流れ始めます。
- 実機のWi-Fiオフ・モバイル通信オフ: 屋内で実機テストすると、GPSの初期捕捉に時間がかかります。タイムアウトが3秒だと取れません。
getCurrentPositionAsync({ timeout: 15000 })のように長めに設定するか、ネットワーク経由のフォールバックを検討してください。 - Android実機の位置情報サービスがオフ: Androidは「アプリの権限」が許可でも、デバイス全体の位置情報サービスがオフだと取れません。
Location.hasServicesEnabledAsync()で事前チェックし、オフならユーザーに設定アプリを開いてもらうUIを出します。
async function getLocationSafely() {
// Android: デバイス全体の位置情報サービスがオンか
const enabled = await Location.hasServicesEnabledAsync();
if (!enabled) {
Alert.alert('位置情報サービスがオフです', 'デバイスの設定で位置情報をオンにしてください。');
return null;
}
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') return null;
try {
const location = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.Balanced, // 省電力寄りで十分なケースが多い
timeout: 15000,
});
return location;
} catch (error) {
console.error('位置取得失敗:', error);
return null;
}
}Location.Accuracy.Highest をデフォルトで使うと初期捕捉が遅くなり、屋内では失敗しやすくなります。地図アプリのように現在地を中心に表示する用途なら、Balanced か High で十分なことが多いです。
ポイント5: バックグラウンド位置情報の罠
「ランニング中に画面を消しても記録を続けたい」「フードデリバリーの配達員を追跡したい」といった用途では、バックグラウンドでも位置情報を取り続ける必要があります。ここは設定が一段難しくなり、Rorkの初期コードでは通常カバーされない部分です。
iOSでは app.json の isIosBackgroundLocationEnabled を true にし、Info.plistに UIBackgroundModes として location が含まれている必要があります。さらに、ユーザーには「常に許可」を選んでもらう必要があり、これはAppleが特に厳しく審査する領域です。「アプリ使用中のみ」しか許可されていない状態でバックグラウンド位置取得APIを呼ぶと、無音で失敗します。
// バックグラウンド許可のリクエストは前景許可のあとに行う
async function requestBackgroundPermission() {
const fg = await Location.requestForegroundPermissionsAsync();
if (fg.status !== 'granted') return false;
// iOSでは前景許可後に少し時間を置いてから要求する方が成功率が高い
await new Promise((r) => setTimeout(r, 500));
const bg = await Location.requestBackgroundPermissionsAsync();
return bg.status === 'granted';
}App Storeの審査では、バックグラウンド位置情報を使う理由が機能から自然に伝わる必要があります。「常に許可」が必要ない用途であれば、潔くフォアグラウンド許可だけに絞った方が、審査も通りやすく、ユーザーの許可率も上がります。
トラブルシューティングの確認順チェックリスト
最後に、位置情報が取れないときの確認順を簡潔にまとめます。
- まず
getForegroundPermissionsAsync()で現在の権限状態をログに出す - 設定アプリで該当アプリの「位置情報」項目があるか確認する
app.jsonのexpo-locationplugin設定が正しく書かれているか確認するnpx expo prebuild --clean後にEAS Buildで作り直したか確認する- シミュレータならLocationの初期値、実機なら位置情報サービスのON/OFFを確認する
- タイムアウトを15秒程度に伸ばし、accuracyを
Balancedに下げて再試行する - バックグラウンド要件があるかを再検討し、不要なら前景のみに絞る
位置情報のトラブルは原因が分散しているので、闇雲にコードを書き換えるよりもこの順番で切り分けていく方が明確に早く解決できます。
Rorkで作ったアプリの権限設計についてもう少し体系的に学びたい方は、Rorkアプリで権限が取れないときのトラブルシューティング も合わせてご覧ください。位置情報以外の権限(カメラ・通知・写真ライブラリなど)にも応用できる考え方が詰まっています。
位置情報・カメラ・プッシュ通知などを横串で理解したい開発者にとって参考になります。
権限まわりは「動けばよい」で済ませると、後でストア審査やレビューでつまずきます。今日のうちに、自作アプリ1本で getForegroundPermissionsAsync() をログに出すところから始めてみてください。原因の所在が一気に見えるようになります。