# How to Add a Currency Converter to Your App (Copy-Paste Prompts Included)

- Tool: App Features
- Last updated: July 2026

## TL;DR

A currency converter needs five components: a currency input pair with a swap button, an exchange rate API client (ExchangeRate-API, Open Exchange Rates, or Frankfurter), a rate cache layer with a timestamp, a bundled currency list with ISO 4217 codes and flag emoji, and an optional rate history chart. With FlutterFlow you can ship a working converter in 1–3 hours. Running costs are $0 for 100–1,000 users with smart caching; $12–50/mo at 10,000 users using a shared server-side cache.

## What a Currency Converter Feature Actually Is

A currency converter is deceptively simple to describe — pick two currencies, enter an amount, see the result — but the implementation has several traps that catch first builds: CORS blocks on direct API calls from browsers and FlutterFlow web builds, floating-point rounding that shows incorrect decimals for certain currency pairs, stale rates displayed silently after API failures, and currency lists that are too long to be usable without search. The exchange rate data itself comes from a REST API refreshed every 1–24 hours; the real engineering is the cache layer that prevents per-user API calls from exhausting your quota, and the decimal precision rules (JPY has 0 decimal places, KWD has 3) that make results look right for every currency.

## Anatomy of the Feature

Five components. The rate cache layer and decimal precision handling are where most first builds fail. Read both before prompting.

- **Currency input pair** (ui): Two amount fields with currency selector dropdowns and a swap button in between. In Flutter: TextField widgets with numeric keyboard type plus a custom keypad widget for calculator-style input. Currency selectors open a bottom sheet with search input and a scrollable list. The swap button simultaneously exchanges both currency codes and recalculates both amounts without resetting the input.
- **Exchange rate API client** (service): HTTP GET to ExchangeRate-API (https://v6.exchangerate-api.com/v6/{key}/latest/USD), Open Exchange Rates (https://openexchangerates.org/api/latest.json?app_id={key}), or Frankfurter API (https://api.frankfurter.app/latest — free, no key, ECB rates only). Returns a JSON object with currency codes as keys and exchange rates relative to the base currency as values.
- **Rate cache layer** (data): sqflite or Hive (Flutter) stores the last-fetched rates JSON with a fetched_at timestamp. On app launch, the app reads the cache first — if fetched_at is less than 1 hour ago, skip the API call. If cache is over 6 hours old, show a staleness warning banner. If the API call fails and cache exists, use the cache and show the banner. If the API fails and no cache exists, show a full error state with a retry button.
- **Currency list** (data): A static JSON file bundled in the app's assets folder containing ISO 4217 currency codes, full currency names, and flag emoji. Filterable via a search input in the currency selector bottom sheet. Top 10 currencies pinned at the top (USD, EUR, GBP, JPY, CAD, AUD, CHF, CNY, INR, MXN).
- **Rate history chart** (ui): fl_chart Flutter package renders a 7-day or 30-day sparkline of historical rates for the selected currency pair. Historical data comes from the ExchangeRate-API history endpoint (/history?start=YYYY-MM-DD&end=YYYY-MM-DD) or Frankfurter (free, ECB historical rates). Displayed below the conversion result as a collapsible widget.

## Data model

Lightweight local-first data model — rates are cached on-device; only user favorites (optional) go to Supabase. The sqflite schema lives in the Flutter app:

```sql
-- Supabase table for user favorites (optional, skip for offline-only builds)
create table public.user_currency_favorites (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users(id) on delete cascade not null,
  currency_code text not null check (length(currency_code) = 3),
  display_order integer not null default 0,
  created_at timestamptz not null default now(),
  unique (user_id, currency_code)
);

alter table public.user_currency_favorites enable row level security;

create policy "Users can manage own favorites"
  on public.user_currency_favorites for all
  using (auth.uid() = user_id)
  with check (auth.uid() = user_id);

-- Device-local sqflite schema (defined in Flutter Dart code, not Supabase)
-- Table: cached_rates
-- Columns: base_currency TEXT PRIMARY KEY, rates_json TEXT, fetched_at INTEGER (Unix timestamp)
-- Table: rate_history
-- Columns: id INTEGER PRIMARY KEY AUTOINCREMENT, pair TEXT, date TEXT, rate REAL
```

For an offline-only converter with no user accounts, skip the Supabase table entirely and store favorites in shared_preferences with currency codes as a JSON list. The sqflite cached_rates table lives in the device's local SQLite database — no server required for caching. Only add Supabase if users need their favorites to persist across devices.

## Build paths

### Flutterflow — fit 5/10, 1–3 hours

Best fit for mobile — FlutterFlow's visual builder handles the input fields and currency selector UI; HTTP GET actions fetch rates; custom functions handle caching and decimal precision.

1. Create the converter page in FlutterFlow: two TextField widgets (numeric keyboard) with currency selector Dropdowns next to each, a swap icon button between them, and a 'Last updated' text widget below
2. Add an HTTP GET API call action in the Action editor pointing to https://api.frankfurter.app/latest (no API key required) with a success action that stores the rates JSON in Page State variable ratesJson
3. Add a Custom Function convertAmount(double amount, String from, String to, String ratesJson) that parses the JSON, performs the currency conversion using integer math for precision, and returns the formatted string — handle JPY (0 decimal places) and KWD (3 decimal places) explicitly
4. Add a Custom Function getCachedRates() and setCachedRates() using the sqflite or shared_preferences package to store rates with a timestamp; call getCachedRates on page init and only trigger the HTTP action if cache is older than 1 hour
5. Wire the swap button to an action that swaps the two currency Page State variables and triggers convertAmount with the swapped values
6. Add a currency search bottom sheet: a ListView with the bundled currency list filtered by a TextField search input; top 10 currencies shown first using a sorted list custom function

Limitations:

- Complex sqflite caching (with a separate rate_history table for charts) requires a custom code block — shared_preferences is simpler but has less storage capacity
- Rate history chart (fl_chart) requires a custom widget; no built-in chart widget in FlutterFlow covers sparklines
- FlutterFlow web build will hit CORS errors calling Frankfurter directly — only the native mobile build bypasses CORS; test on device
- FlutterFlow Pro or Teams required for custom code blocks if sqflite or fl_chart packages are needed

### Lovable — fit 2/10, 2–4 hours

Web-only path — suitable for a currency widget embedded in a web product, not a standalone mobile converter. Rate API calls must go through a Supabase Edge Function proxy to avoid CORS.

1. Create a Lovable project and connect Lovable Cloud
2. Prompt for a Supabase Edge Function that fetches rates from Frankfurter, caches the response in a Supabase table for 1 hour, and returns the rates JSON — this proxy eliminates CORS issues on web
3. Build the web converter UI: two amount inputs with currency selectors and a swap button; call the Edge Function on page load and on manual refresh
4. Store user-pinned currencies in Supabase user_currency_favorites table with RLS
5. Display the last-updated timestamp below the converter with a 'Rates may be up to 1 hour old' note

Starter prompt:

```
Build a web currency converter widget. Add a Supabase Edge Function called get-rates that fetches from Frankfurter API, caches the response in a Supabase cached_rates table (base_currency, rates jsonb, fetched_at timestamptz) for 1 hour, and returns rates JSON — this eliminates CORS. Frontend: two amount inputs with currency selector dropdowns; pre-pin USD, EUR, GBP, JPY, CAD, AUD, CHF, CNY, INR, MXN at the top. Swap button exchanges both fields and their currencies simultaneously. Debounce conversion recalculation 300 ms after last keystroke. Show last-updated timestamp ('Rates from X minutes ago') and a staleness warning when cache age exceeds 6 hours. Handle JPY as zero-decimal (display 123, not 123.00). Show 'Rates unavailable — showing last cached rates' when the Edge Function fails. Store user-pinned currency pairs in Supabase user_currency_favorites with RLS.
```

Limitations:

- Web browser cannot call Frankfurter or ExchangeRate-API directly due to CORS — all API calls must proxy through a Supabase Edge Function
- No native numeric keypad or mobile-optimized UX — this is a web widget, not a mobile app experience
- Offline caching is not possible in a browser web app the same way as in a native mobile app — users on spotty connections see errors instead of cached rates
- Rate history chart requires a charting library (Recharts) wired to a historical rates Edge Function — adds 1–2 hours of additional work

### Custom — fit 4/10, 2–4 days

Full offline support, custom chart library, per-currency decimal precision conforming to ISO 4217 spec, home screen widget, and push notification alerts when a target rate is hit.

1. Full offline-first architecture: sqflite stores rate history by pair and date; app works completely without connectivity using cached data from the last successful fetch
2. Custom decimal precision implementation using Dart's decimal package — correct rounding for all ISO 4217 currencies (0 to 3 decimal places) without floating-point errors
3. Home screen widget (home_widget Flutter package) showing the user's pinned pair and current rate without opening the app
4. Rate alert push notification: background fetch checks if target rate has been reached and sends a local notification via flutter_local_notifications

Limitations:

- Home screen widget (WidgetKit on iOS, AppWidgetProvider on Android) requires platform-specific native code — adds 1–2 days
- Real-time tick-by-tick forex rates for trading apps require a WebSocket feed (not available on free API plans) — this is a separate architectural decision

## Gotchas

- **CORS error calling exchange rate API from browser or FlutterFlow web** — Exchange rate APIs like ExchangeRate-API and Open Exchange Rates do not send CORS headers that allow browser-originated requests. Any attempt to call them directly from a web browser — including FlutterFlow's web build preview — returns a CORS error and the rates never load. This affects Lovable builds and any FlutterFlow app tested in a browser. Fix: On mobile native builds (iOS/Android), Flutter's HTTP client bypasses CORS — the native build works fine. For web builds, route all API calls through a Supabase Edge Function that fetches the rates server-side and returns them with appropriate CORS headers. FlutterFlow HTTP actions in native builds are not affected, but test specifically on native, not in the FlutterFlow browser preview.
- **Stale rates displayed without any warning after API failure** — AI tools typically catch API errors silently and fall through to displaying cached rates from however long ago they were last fetched — sometimes 24 hours or more. A user making a financial decision sees rates labeled as 'Live' that are actually from the previous day's market close. For a currency converter, this is not just a UX problem — it can cause real financial loss if the user acts on wrong rates. Fix: Always display the cache age, not just the fetch status. Calculate the time since fetched_at and show 'Rates from [X hours] ago' as a persistent subtitle under the conversion result. If cache age exceeds 6 hours, change the color to amber and show a 'Tap to refresh' indicator. Differentiate explicitly between fresh data (green indicator) and cached data (grey or amber indicator with age).
- **Floating-point rounding errors in converted amounts** — Dart and JavaScript use IEEE 754 double-precision floating-point for arithmetic. Classic example: 1.1 + 2.2 evaluates to 3.3000000000000003. In a currency converter, multiplying 100 USD by a JPY rate of 149.87 might yield 14986.999999998 instead of 14987.00. Displaying this raw result shows garbage digits to the user. Fix: Use integer math for conversion: convert the input amount to minor units by multiplying by 100,000, perform the rate multiplication as integer arithmetic, then divide back down and format. Alternatively, use Dart's decimal package which implements arbitrary-precision decimal arithmetic. Format the final result using toStringAsFixed() with the correct decimal count for each currency — 0 for JPY/KRW/VND, 2 for most currencies, 3 for KWD/BHD/OMR.
- **Rate history endpoint not available on free plan — shows 403 error** — AI tools sometimes scaffold the rate history sparkline and call the historical endpoint without checking whether it is included in the API plan. ExchangeRate-API's historical endpoint requires a paid plan — the free tier returns 403 Forbidden. The chart either shows nothing or breaks the page with an unhandled error. Fix: Check your ExchangeRate-API plan before building the history chart. Free alternative: use the Frankfurter API (api.frankfurter.app/{start-date}..{end-date}?from=USD&to=EUR) which provides ECB historical rates for major currencies at no cost. For non-ECB currencies, either upgrade the plan or disable the history chart on free builds with a 'Upgrade for rate history' placeholder.
- **Currency selector list shows all 170+ currencies with no search** — AI tools generate a flat ListView of all ISO 4217 currencies in alphabetical order. Finding USD requires scrolling past A, B, and C; finding JPY requires scrolling through the J section. On mobile with a small screen, users give up and abandon the feature. The currency list is the primary interaction in a converter — a bad selector kills the whole experience. Fix: Structure the currency selector as: 10 pinned currencies at the very top (USD, EUR, GBP, JPY, CAD, AUD, CHF, CNY, INR, MXN), a divider, a search TextField that filters the remaining list in real time, then the full list sorted alphabetically. Show only 30 currencies in the collapsed list with a 'Show all 170 currencies' expansion. This covers 95% of lookup queries without requiring any scrolling.

## Best practices

- Cache rates on-device with a 1-hour TTL — per-user API calls at scale exhaust free tiers instantly and the difference between 1-hour-old and live rates is negligible for most use cases
- Always show the cache age, not just a generic 'Live rates' label — users making financial decisions need to know how fresh the data is
- Use integer arithmetic or Dart's decimal package for currency math — never use double multiplication for monetary amounts
- Handle per-currency decimal precision explicitly: JPY, KRW, VND = 0 decimal places; KWD, BHD, OMR = 3 decimal places; everything else = 2
- Pin the top 10 currencies at the head of the selector and add real-time search filtering — the full 170-country flat list is unusable on mobile
- Re-fetch rates when the app resumes from background after more than 1 hour — rates change during extended background sessions
- Test the offline state explicitly: disable Wi-Fi and confirm cached rates display with a staleness banner, not a blank screen or crash
- On a shared server-side cache (Supabase Edge Function), fetch once per hour for all users and return the cached result — this eliminates per-user API calls entirely at 10,000+ user scale

## Frequently asked questions

### Which exchange rate API is most accurate?

For general consumer apps, ExchangeRate-API and Open Exchange Rates source from the same commercial forex data feeds and are functionally equivalent in accuracy — within 0.1% of interbank rates. Frankfurter uses ECB (European Central Bank) rates, which update only on business days and may lag by one day on weekends. For trading or financial applications requiring tick-by-tick accuracy, these REST APIs are all insufficient — you need a real-time WebSocket feed from a financial data provider.

### How often should exchange rates refresh?

For a general consumer currency converter, once per hour is the right balance — rates do not move enough in an hour to affect everyday conversions, and hourly caching keeps your API calls well within free tier limits. Travel apps that need same-day accuracy can refresh every 30 minutes. Do not refresh more often than every 15 minutes on free API tiers — you will hit rate limits. For forex trading, none of these REST APIs are appropriate; you need a real-time streaming feed.

### Can the converter work offline?

Yes, with on-device caching. Store the last-fetched rates JSON in sqflite or shared_preferences with a fetched_at timestamp. On app launch, check connectivity — if offline, read from the cache and show a 'Rates from [X hours ago] — no internet connection' banner. The converter remains fully functional with cached rates. Users making offline travel conversions understand they are seeing rates from the last sync; just be transparent about it.

### How do I handle currencies with no decimal places like Japanese Yen?

Format the conversion result using toStringAsFixed(0) for JPY, KRW, and VND — these currencies have no minor units. Show '14,987 JPY' not '14,987.00 JPY'. Similarly, KWD (Kuwaiti Dinar), BHD (Bahraini Dinar), and OMR (Omani Rial) use 3 decimal places — show '37.450 KWD'. Build a decimal_places map keyed by ISO 4217 code and look up each currency's precision before formatting. This prevents the wrong decimal display that makes users distrust the converter.

### Is my app liable if the rate is slightly wrong?

No, as long as you display a disclaimer that rates are for informational purposes only and may not reflect actual transaction rates. All consumer currency converter apps (Google, XE, Wise) include this disclaimer. If your app is used to execute actual currency transactions or financial products, you need to comply with financial services regulations in your jurisdiction — that is a different product category requiring legal review.

### Can I build rate alerts that notify users?

Yes, but it requires background processing. The app needs to check the target rate against the current rate periodically — either via background fetch (BackgroundFetch Flutter package) or a server-side cron job that sends push notifications via FCM/APNs. Background fetch on iOS is restricted and unreliable for precise timing. A server-side approach (Supabase Edge Function on a cron schedule) is more reliable: check all user alert targets every 15 minutes and send a push via Firebase Cloud Messaging when a target is hit.

### How do I display the correct currency symbol vs code?

Use the Dart intl package's NumberFormat.simpleCurrency(name: currencyCode) to get locale-aware currency formatting. It automatically uses '$' for USD, '€' for EUR, '£' for GBP, and '¥' for both JPY and CNY. For displaying in the currency selector, show the ISO 4217 code (USD, EUR) alongside the symbol and flag emoji — the code is unambiguous, while symbols like '$' are shared by USD, CAD, AUD, and many others.

### Can I add a historical rate chart?

Yes, using the fl_chart Flutter package for the sparkline and either the ExchangeRate-API history endpoint (paid plans) or the Frankfurter API (free, ECB historical rates for 33 major currencies). Fetch 7 or 30 days of daily rates for the selected pair, cache them in sqflite, and render a line chart below the conversion result as a collapsible section. Note that Frankfurter only covers EUR-base currencies and major pairs — for exotic pair history, you need a paid plan from ExchangeRate-API or Open Exchange Rates.

---

Source: https://www.rapidevelopers.com/app-features/currency-converter
© RapidDev — https://www.rapidevelopers.com/app-features/currency-converter
