RORK LABJP
TOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the roundTOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the round
Articles/Dev Tools
Dev Tools/2026-04-26Intermediate

Build a Store Finder App with Rork Using Google Places API: A Practical Location Integration Guide

Learn how to build a location-based store finder app with Rork and Google Places API. Covers current location access, nearby place search, and map markers — with working code and real troubleshooting tips.

Google Places APIlocationmapsReact Native209Expo149external API2

One pattern that comes up constantly in Rork projects: at some point, almost everyone wants some version of "show me what's nearby." A café finder, a local services directory, a travel guide — location plus place search is one of the most satisfying features to build, and also one of the areas where the setup has a few non-obvious steps.

This guide walks through building a working "find nearby cafés and display them on a map" feature using Google Places API and react-native-maps. The code is ready to use as-is, and I'll flag the two spots where most people hit a wall.

Setting Up Your Google Places API Key

Before writing any Rork code, get the API key set up in Google Cloud Console.

  1. Go to Google Cloud Console and create a new project
  2. In "APIs & Services" → "Library", search for "Places API" and enable it
  3. In "Credentials", create an API key. For production apps, restrict the key to your iOS Bundle ID or Android package name
# Add to your .env file — never hardcode API keys in source
EXPO_PUBLIC_GOOGLE_PLACES_API_KEY=YOUR_GOOGLE_PLACES_API_KEY

The EXPO_PUBLIC_ prefix makes the variable available in React Native code via process.env.EXPO_PUBLIC_GOOGLE_PLACES_API_KEY, with no extra configuration needed. Make sure .env is in your .gitignore.

One thing worth noting upfront: a valid API key is necessary but not sufficient. Google Places API requires an active billing account linked to your Cloud project. Without it, requests will be denied even with a correct key — this is the source of a lot of confusion, so I'll come back to it in the troubleshooting section.

Creating the Project in Rork

Open Rork Companion and use a prompt like this to generate the initial structure:

Create an app that gets the user's current location and searches for cafés 
within 500 meters using the Google Places API. Display results as markers 
on a map. Tapping a marker should show the place name and address.
Use fetch for API calls and manage state with useState and useEffect.

When Rork generates the code, check that expo-location is in the dependencies. This package handles both the OS permission dialog and the actual coordinate retrieval — without it, you can't access location data.

Implementing Location Access and Place Search

Separating the location and API logic into a custom hook keeps the map component focused on rendering. Here's the hook:

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 () => {
      // Request foreground location permission
      const { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== 'granted') {
        setError('Location permission was not granted');
        setLoading(false);
        return;
      }
 
      // Get current position
      const location = await Location.getCurrentPositionAsync({
        accuracy: Location.Accuracy.Balanced,
      });
      const { latitude: lat, longitude: lng } = location.coords;
      setUserLocation({ lat, lng });
 
      // Search for nearby cafés with Google Places API
      const apiKey = process.env.EXPO_PUBLIC_GOOGLE_PLACES_API_KEY;
      const radius = 500; // meters
      const type = 'cafe';
      const url =
        `https://maps.googleapis.com/maps/api/place/nearbysearch/json` +
        `?location=${lat},${lng}&radius=${radius}&type=${type}&key=${apiKey}`;
 
      const response = await fetch(url);
      if (!response.ok) {
        setError('Failed to load nearby places');
        setLoading(false);
        return;
      }
 
      const data = await response.json();
      // Expected response shape: { results: [...], status: "OK" }
      setPlaces(data.results ?? []);
      setLoading(false);
    })();
  }, []);
 
  return { places, userLocation, error, loading };
}

The error handling covers both permission denial and network failures. In practice, both will happen — permission dialogs get dismissed, and network conditions vary — so handling them explicitly prevents cryptic crashes in production.

Rendering Markers on the Map

With the hook handling the data, the map component stays clean:

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}>Finding your location...</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 },
});

What to expect: The map opens centered on the user's current location. Red pins appear for cafés within 500m. Tapping a pin shows the place name and address in a callout bubble.

Two Places Where People Get Stuck

ZERO_RESULTS or REQUEST_DENIED from the API: This almost always means the billing account isn't linked. A valid API key alone isn't enough — Places API requires an active billing account in Google Cloud Console. Go to Billing and connect a payment method. There's a generous free tier, so you won't incur actual charges for a dev project, but the account still needs to be set up. Without it, the API silently rejects requests even when the key is correctly configured.

Blank white map on Android: When using PROVIDER_GOOGLE with react-native-maps, Android requires a google-services.json file in the project root. If it's missing, the map renders as a blank white screen — no error message, no crash. iOS doesn't have the same requirement, but Android builds will fail silently without it. Check this before your first Android build.

For a broader look at patterns that apply across different external services, the External API Integration Guide covers request handling, error states, and environment variable management in more depth.

Where to Take the App Next

The app you've built at this point is a working MVP — a real device, real pins, real nearby cafés. Some directions worth exploring from here:

Adding a category switcher (café / restaurant / convenience store / pharmacy) only requires changing the type parameter in the API URL. It's a small change that significantly broadens what the app can do.

For saving favorite places, the Supabase spot-sharing tutorial shows how to store and share location data with persistent state — including the social layer if you want users to share lists with each other.

Place details are available via a separate endpoint that takes place_id as input. This unlocks photos, opening hours, ratings, and phone numbers — everything needed to build a detail screen that's worth releasing.

The First Step

Get the API key, link the billing account, and paste this code into a new Rork project. Running the app on a real device and seeing actual pins appear near your current location is a different experience than working with mock data — it makes the next set of features much easier to think through concretely.

Location-based apps have a tangible quality to them. Worth experiencing early.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-07-15
The Comma That Fell to the Start of a Line: Rork, Quote Cards, and Japanese Line Breaking
React Native's Text component does not guarantee Japanese line-breaking rules. Here are the violation rates I measured, a WORD JOINER implementation that survives copy and VoiceOver, an onTextLayout audit harness, and how Rork Max (SwiftUI) differs.
Dev Tools2026-07-10
Adding React Compiler to Expo Let Me Delete 41 Hand-Written memo Calls
I enabled React Compiler on Rork-generated React Native screens and measured the rerender counts with Profiler. Here is how I decided which memo and useCallback calls were safe to delete, how to find the components the compiler bailed out on, and how to catch regressions in CI.
Dev Tools2026-07-09
When Amplitude's Funnel Improved Overnight — Field Notes on Catching Event Schema Drift With a Self-Audit
In a Rork-built app, the Amplitude funnel showed signup-to-purchase conversion jumping from 8% to 19% overnight, yet revenue stayed flat. The cause was a property-name drift that inflated the count. Field notes on measuring the drift with an event contract and a runtime validator, then tightening it in stages.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →