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 連携の基本ガイド.