◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/Dev Tools
⬡ Dev Tools/2026-05-05Beginner

Build a Real-Time Currency Converter App with Rork — API Integration, Home Screen Widget, and Error Handling

A practical tutorial for building a currency converter app from scratch with Rork. Covers ExchangeRate-API integration, home screen widget support, and handling common errors gracefully.

Rork585app development39external APIcurrency converterwidgetReact Native239tutorial20

A currency converter is one of the best first projects to build in Rork. It looks simple on the surface, but it covers the full range of skills you need — fetching from an external API, managing state, designing a clean UI, and handling errors gracefully.

When I was first working through what it means to actually ship an app, projects at this scale turned out to be the most efficient way to learn. Here's how to build one from scratch, including a home screen widget.

What We're Building

The finished app will include:

  • Live exchange rates for major currencies (JPY / USD / EUR / GBP / CNY)
  • Instant conversion as you type
  • A home screen widget (iOS only) showing key rates
  • Error fallback with retry when the network is unavailable

The widget requires Rork Max, but the main app works on the free Rork plan.

Step 1: Set Up ExchangeRate-API

For exchange rate data, we'll use ExchangeRate-API (https://www.exchangerate-api.com/). The free plan gives you 1,500 requests per month, which is more than enough for a personal project.

Sign up, copy your API key from the dashboard, and note the request format:

GET https://v6.exchangerate-api.com/v6/{YOUR_API_KEY}/latest/USD

A successful response looks like this:

{
  "result": "success",
  "base_code": "USD",
  "conversion_rates": {
    "JPY": 153.42,
    "EUR": 0.9187,
    "GBP": 0.7892,
    "CNY": 7.2341
  }
}

Step 2: Generate the Main Screen in Rork

Paste the following prompt into Rork. Keeping the scope focused — one screen at a time — produces cleaner code than asking for everything at once.

Create the main screen for a currency converter app.

Requirements:
- A picker at the top for the base currency (USD / EUR / JPY / GBP / CNY)
- A numeric input field for the amount
- A list showing converted values in all five currencies
- Skeleton loading state while fetching
- An error state with a "Retry" button

Design:
- System font, clean card-based layout
- Full light/dark mode support

Once the screen is generated, add the API fetch function by prompting Rork to integrate it:

// Fetch live exchange rates
// Store the API key in EXPO_PUBLIC_EXCHANGE_API_KEY (environment variable)
const fetchExchangeRates = async (baseCurrency: string): Promise<Record<string, number>> => {
  const apiKey = process.env.EXPO_PUBLIC_EXCHANGE_API_KEY;
 
  if (!apiKey) {
    throw new Error('API key is missing. Add EXPO_PUBLIC_EXCHANGE_API_KEY to your .env file.');
  }
 
  const response = await fetch(
    `https://v6.exchangerate-api.com/v6/${apiKey}/latest/${baseCurrency}`
  );
 
  if (!response.ok) {
    throw new Error(`API error: ${response.status}`);
  }
 
  const data = await response.json();
 
  if (data.result !== 'success') {
    throw new Error(`Fetch failed: ${data['error-type'] ?? 'Unknown error'}`);
  }
 
  return data.conversion_rates;
};

Why use an environment variable? Hardcoding the API key directly in your source code exposes it if you ever push to GitHub. Add it through Rork's Settings > Environment Variables instead.

Step 3: State Management and Live Conversion Logic

For real-time conversion as the user types, a combination of useEffect and useMemo works well. Prompt Rork to add this structure:

Add the following state to the currency converter component:

- baseCurrency (string): currently selected base currency
- inputAmount (string): the amount the user typed (keep as string)
- rates (Record<string, number>): fetched exchange rates
- isLoading (boolean): true while fetching
- error (string | null): error message if fetch fails

Calculate converted values with useMemo so they only recompute
when baseCurrency or rates change.

One thing worth double-checking in the generated code: inputAmount should be stored as a string, not a number. Storing it as a number breaks the input when the user types something like 0. or 0.05 — the decimal gets dropped mid-input. Keep the raw string and parse it only when calculating the result.

Step 4: Three Errors You'll Probably Hit

1. Conversion rates come back as undefined

The keys in conversion_rates are uppercase currency codes ("JPY", not "jpy"). If your picker emits lowercase codes, the lookup will silently return undefined.

// ❌ Lowercase lookup returns undefined
const rate = rates["jpy"];
 
// ✅ Match the uppercase keys from the API
const rate = rates["JPY"];

2. A failed fetch crashes the app instead of showing an error

Missing error handling around fetch is a common source of crashes. Wrap the call in try-catch and always reflect the error in state:

const loadRates = async () => {
  setIsLoading(true);
  setError(null);
  try {
    const newRates = await fetchExchangeRates(baseCurrency);
    setRates(newRates);
  } catch (err) {
    // Store as string — don't setState with an Error object directly
    setError(err instanceof Error ? err.message : 'An unexpected error occurred');
  } finally {
    setIsLoading(false);
  }
};

3. Widget rates don't match the main app

Widgets run in a separate process and can't read your app's in-memory state. Any data you want to share must go through a persistent store. Step 5 covers this.

Step 5: Adding a Home Screen Widget (Rork Max Required)

Widgets are generated by Rork Max using its SwiftUI native generation. Use this prompt:

Add a home screen widget to the currency converter app.

Specs:
- Size: small (2x2) only
- Display: USD/JPY and EUR/JPY rates
- Refresh: every 60 minutes using TimelineEntry
- Data source: read rates from an AppGroup shared container

Implement with SwiftUI and WidgetKit.

Sharing data between the widget and the main app requires an App Group. After enabling it in Xcode under Signing & Capabilities, add this to the main app's rate-fetching code:

// Save rates for the widget after a successful fetch
const saveRatesForWidget = (rates: Record<string, number>) => {
  const widgetData = {
    usdJpy: rates['JPY'],
    eurJpy: rates['JPY'] / rates['EUR'],  // Cross rate calculation
    updatedAt: new Date().toISOString(),
  };
  // Write to shared container via the NativeModule Rork Max generates
  SharedPreferences.setSharedValue('widgetRates', JSON.stringify(widgetData));
};

Once the widget is working, you can refine its appearance entirely through Rork Max prompts — "make it more compact", "increase the font size", "add a last-updated timestamp" all work well.

Before You Ship

Two quick things to sort out before submitting to the App Store:

Rate caching: The free tier gives you 1,500 requests per month. If your app fetches on every open, that runs out fast with even modest usage. Store the last fetch timestamp in AsyncStorage and only refetch after 30–60 minutes. It's a small addition that keeps you well inside the free limit.

Disclaimer text: Adding a note like "Rates are for reference only and may differ from actual transaction rates" in the app footer is good practice. It's also a minor detail that can prevent an App Store rejection on financial apps.

From here, natural next steps are adding a historical rate chart or a multi-currency comparison view. The ExchangeRate-API paid plan includes historical data endpoints, and Rork's AI generation makes adding charts a matter of a few prompts.

The pattern you've learned here — fetch, handle errors, cache, share data with a widget — applies directly to weather, stocks, news, and just about any other live-data app you'll want to build next.

For more on connecting to external APIs in Rork, see the 外部 API 連携の基本ガイド.

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-09-04
EAS secret visibility does not keep a value out of your app — deciding prefix and visibility separately
The EXPO_PUBLIC_ prefix decides what ships inside your app; EAS visibility decides who can read it. Why stacking them blanks a value on OTA updates, and how to check your build.
⬡ Dev Tools2026-08-22
Every bulk replace exited zero. The damage was in the lines I did not delete
Run a bulk replace over generated code and the breakage lands on the neighbouring lines, not the matched ones. Here is what broke in a live project, and a dependency-free guard that checks the invariants a replace must preserve.
⬡ Dev Tools2026-08-14
Find the native edits expo prebuild will erase before you upgrade to SDK 57
Expo SDK 57 makes expo prebuild clear and regenerate ios and android by default. Here is how to audit your hand edits first, move them into config plugins, and why 57.0.9 matters for Reanimated apps.