Rork でアプリを作り始めると、いずれ「現在地から近くのお店を探せる機能が欲しい」という場面に出会います。
飲食店検索、観光スポット案内、近くのサービス一覧 — 位置情報とスポット検索を組み合わせたアイデアは豊富です。ただ、地図コンポーネントと外部 API を同時に動かすのは、初めてだと少し複雑に感じるかもしれません。
完成したコードはそのまま使えるよう、コメントと期待する動作も記載しています。
Google Cloud で API キーを用意する
まず Google Cloud Console から Places API を有効化し、API キーを取得します。
- Google Cloud Console にアクセスし、新しいプロジェクトを作成します
- 「API とサービス」→「ライブラリ」で「Places API」を検索して有効化します
- 「認証情報」から API キーを作成します。本番運用では、アプリのバンドル ID(iOS)またはパッケージ名(Android)で制限をかけることをおすすめします
# .env ファイルに追記(API キーはコードに直接書かない)
EXPO_PUBLIC_GOOGLE_PLACES_API_KEY=YOUR_GOOGLE_PLACES_API_KEYEXPO_PUBLIC_ プレフィックスをつけると、React Native のコード内でそのまま process.env.EXPO_PUBLIC_GOOGLE_PLACES_API_KEY として参照できます。.gitignore に .env を追加するのも忘れずに。
なお、API キーを取得しただけでは不十分で、Google Cloud の課金アカウントを有効化しておく必要があります。これを見落とすと、後述する REQUEST_DENIED エラーの原因になります。
Rork でプロジェクトを作成する
Rork Companion を起動し、以下のようなプロンプトで初期構成を生成します。
現在地を取得して、半径 500m 以内のカフェを検索し、
地図上にマーカーで表示するアプリを作ってください。
マーカーをタップすると店名と住所が表示される構成にしてください。
API 呼び出しには fetch を使い、状態管理は useState と useEffect で行ってください。
生成されたコードの中で最初に確認すべきは、expo-location が依存関係に含まれているかどうかです。位置情報の取得にはこのパッケージが必要で、OS のパーミッション処理も含まれています。
現在地の取得と周辺スポット検索を実装する
位置情報の取得と API 呼び出しをカスタムフックに分離すると、コンポーネント側がすっきりします。
import * as Location from 'expo-location';
import { useState, useEffect } from 'react';
type Place = {
place_id: string;
name: string;
vicinity: string;
geometry: {
location: { lat: number; lng: number };
};
};
export function useNearbyPlaces() {
const [places, setPlaces] = useState<Place[]>([]);
const [userLocation, setUserLocation] = useState<{ lat: number; lng: number } | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
(async () => {
// パーミッションを要求する
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
setError('位置情報のアクセスが許可されていません');
setLoading(false);
return;
}
// 現在地を取得する
const location = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.Balanced,
});
const { latitude: lat, longitude: lng } = location.coords;
setUserLocation({ lat, lng });
// Google Places API で周辺のカフェを検索する
const apiKey = process.env.EXPO_PUBLIC_GOOGLE_PLACES_API_KEY;
const radius = 500;
const type = 'cafe';
const url =
`https://maps.googleapis.com/maps/api/place/nearbysearch/json` +
`?location=${lat},${lng}&radius=${radius}&type=${type}&language=ja&key=${apiKey}`;
const response = await fetch(url);
if (!response.ok) {
setError('スポット情報の取得に失敗しました');
setLoading(false);
return;
}
const data = await response.json();
// 正常時: { results: [{ name, vicinity, geometry, ... }], status: "OK" }
setPlaces(data.results ?? []);
setLoading(false);
})();
}, []);
return { places, userLocation, error, loading };
}このフックはエラーハンドリングも含んでいます。パーミッション拒否・ネットワーク障害のどちらが起きても error に文字列がセットされ、コンポーネント側でユーザーに通知できます。
地図にマーカーを表示する
フックを使うと、マップコンポーネント自体はシンプルに保てます。
import MapView, { Marker, PROVIDER_GOOGLE } from 'react-native-maps';
import { View, Text, StyleSheet } from 'react-native';
import { useNearbyPlaces } from './useNearbyPlaces';
export default function NearbyPlacesMap() {
const { places, userLocation, error, loading } = useNearbyPlaces();
if (loading) return <Text style={styles.status}>現在地を取得中...</Text>;
if (error) return <Text style={styles.status}>{error}</Text>;
if (!userLocation) return null;
return (
<View style={styles.container}>
<MapView
style={styles.map}
provider={PROVIDER_GOOGLE}
initialRegion={{
latitude: userLocation.lat,
longitude: userLocation.lng,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
}}
showsUserLocation
>
{places.map((place) => (
<Marker
key={place.place_id}
coordinate={{
latitude: place.geometry.location.lat,
longitude: place.geometry.location.lng,
}}
title={place.name}
description={place.vicinity}
/>
))}
</MapView>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1 },
map: { flex: 1 },
status: { flex: 1, textAlign: 'center', marginTop: 100, fontSize: 16 },
});期待する動作: 端末の現在地を中心に地図が表示され、500m 圏内のカフェが赤いピンでマーキングされます。ピンをタップすると店名と住所のポップアップが現れます。
実装でつまずきやすい2つのポイント
ZERO_RESULTS または REQUEST_DENIED が返ってくる場合: API キーの取得だけでは足りません。Google Cloud の課金アカウントが有効になっていないと、正しいキーを使っていてもリクエストが弾かれます。Google Cloud Console の「お支払い」から課金アカウントをリンクし、Places API が「有効」ステータスになっているかを確認してください。無料枠があるので実費が発生するわけではありませんが、アカウントの紐付けは必須です。
Android で地図が白紙になる場合: PROVIDER_GOOGLE を使った MapView は、Android では google-services.json がプロジェクトルートに存在していることが求められます。Rork でビルドする際にこのファイルが配置されていないと、エラーメッセージなしに白紙の画面が表示されます。iOS では同様の問題は起きにくいですが、Android 向けにビルドする場合は事前に確認しておくと安心です。
位置情報アプリの詳細な実装パターンについては、Rork の外部 API 連携ガイドも参考にしてみてください。地図以外の外部サービスとの連携にも応用できる考え方がまとまっています。
アプリをさらに発展させるアイデア
このチュートリアルで作った「近くのカフェを地図に表示するアプリ」は、そのままリリースできる MVP として使えます。次のステップとして試してみると面白いと思うものをいくつか挙げます。
カテゴリ切り替えボタン(カフェ/レストラン/コンビニ)を追加すると、汎用性が大幅に上がります。type パラメーターを変えるだけなので実装は簡単です。
お気に入り登録機能については、Supabase を使ったスポット共有アプリの実装例が参考になります。友人とリストを共有する機能も視野に入るかもしれません。
また、place_id を使って Places API の詳細エンドポイントを叩くと、写真・営業時間・評価・電話番号も取得できます。詳細画面まで作り込むと、リリースできるアプリに近づきます。
全体を振り返って
今日試してみるとしたら、まず Google Cloud Console で API キーを取得して課金アカウントをリンクし、このコードをそのまま Rork に貼り付けてみてください。実際に自分のいる場所の周辺にピンが立つ様子を見ると、次にやりたいことが自然と浮かんできます。
位置情報アプリは、使う人が「いまいる場所」を起点にするため、リアルな体験と直結します。ぜひ動かしてみてください。