Skip to main content
RapidDev - Software Development Agency
App Featurespayments-commerce19 min read

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

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.

4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members

Feature spec

Beginner

Category

payments-commerce

Build with AI

1–3 hours with FlutterFlow or Lovable

Custom build

2–4 days custom dev

Running cost

$0/mo (100 users) · $0–12/mo (1,000 users) · $12–50/mo (10,000 users)

Works on

Mobile

Everything it takes to ship a Currency Converter — parts, prompts, and real costs.

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.

What users consider table stakes in 2026

  • Real-time rate display with a visible last-updated timestamp — users need to know if they are looking at live data or a 6-hour-old cache
  • Swap currencies with a single tap that simultaneously exchanges both the currency selectors and recalculates the amounts — not two separate taps
  • Large numeric input with a calculator-style keypad layout on mobile — standard keyboard obscures the conversion result and frustrates users
  • Common currencies (USD, EUR, GBP, JPY) pinned at the top of the selector — scrolling through 170 countries to find USD is a major UX failure
  • Offline fallback that shows cached rates with a clear staleness warning (e.g., 'Rates from 8 hours ago — tap to refresh') rather than showing an error or blank
  • Rate history sparkline for the selected currency pair showing 7-day or 30-day trend — differentiates from basic web converters

Anatomy of the Feature

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

Layers:UIDataService

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.

Note: The swap animation (rotating the arrow icon 180 degrees while swapping) is a polish detail users notice — include it in your prompt. Use Flutter's AnimatedSwitcher for the icon rotation.

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.

Note: Frankfurter is a good default for beginner builds — free, no API key required, ECB rates. For higher accuracy and more currency coverage, upgrade to ExchangeRate-API paid tier. Note: Open Exchange Rates free tier is USD-base-only, which limits cross-currency pairs.

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.

Note: At 10,000 users without caching, even a 30-minute rate refresh interval would send 20,000 API calls per hour and exhaust any free tier in minutes. The cache is what keeps this feature affordable at scale.

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).

Note: Bundling the currency list as a static asset means no API call is needed to populate the selector. Flag emoji from the bundled list renders everywhere except some older Android versions — test on your minimum supported API level.

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.

Note: Historical data endpoints are often gated behind paid plans. Verify your API tier before building this component. Frankfurter provides free ECB historical rates as an alternative for EUR-based pairs.

The 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:

schema.sql
1-- Supabase table for user favorites (optional, skip for offline-only builds)
2create table public.user_currency_favorites (
3 id uuid primary key default gen_random_uuid(),
4 user_id uuid references auth.users(id) on delete cascade not null,
5 currency_code text not null check (length(currency_code) = 3),
6 display_order integer not null default 0,
7 created_at timestamptz not null default now(),
8 unique (user_id, currency_code)
9);
10
11alter table public.user_currency_favorites enable row level security;
12
13create policy "Users can manage own favorites"
14 on public.user_currency_favorites for all
15 using (auth.uid() = user_id)
16 with check (auth.uid() = user_id);
17
18-- Device-local sqflite schema (defined in Flutter Dart code, not Supabase)
19-- Table: cached_rates
20-- Columns: base_currency TEXT PRIMARY KEY, rates_json TEXT, fetched_at INTEGER (Unix timestamp)
21-- Table: rate_history
22-- Columns: id INTEGER PRIMARY KEY AUTOINCREMENT, pair TEXT, date TEXT, rate REAL

Heads up: 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 it — pick your path

Each tool fits this feature differently. Switch tabs to compare fit, time, the exact steps, and a copy-paste prompt.

Native iOS + AndroidFit for this feature:

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.

Step by step

  1. 1Create 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. 2Add 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. 3Add 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. 4Add 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. 5Wire the swap button to an action that swaps the two currency Page State variables and triggers convertAmount with the swapped values
  6. 6Add 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

Where this path bites

  • 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

Third-party services you'll need

Three exchange rate API options — all have free tiers that cover most early-stage converter apps with smart caching. Choose based on your accuracy and coverage requirements.

ServiceWhat it doesFree tierPaid from
ExchangeRate-APILive and historical exchange rates for 160+ currencies; updated daily or hourly depending on planFree (1,500 requests/mo)$12/mo (100K requests) (approx)
Open Exchange RatesReliable rates API; 200+ currencies; USD-base-only on free tierFree (limited to 1K requests/mo, USD base only)$12/mo unlimited base currency (approx)
Frankfurter APIFree ECB (European Central Bank) rates; 33 major currencies; historical data included; no API key requiredFree, no key required, ECB rates onlyFree (approx)

Swipe the table sideways to see pricing.

What it costs to run

Drag through the tiers to see how your monthly bill scales with users — no surprises later.

Estimated monthly running cost

$0/mo

ExchangeRate-API free tier (1,500 requests/mo) is sufficient when caching rates for 1 hour — 100 users checking rates a few times per day generates under 500 requests. Supabase free tier handles favorites storage.

Estimates use verified 2026 pricing and assume typical usage per user. Your real bill depends on activity, storage, and third-party plans.

What breaks when AI tools build this

The failures people actually hit on their first build — the symptom, why it happens, and the exact fix.

CORS error calling exchange rate API from browser or FlutterFlow web

Symptom: 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

Symptom: 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

Symptom: 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

Symptom: 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

Symptom: 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

1

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

2

Always show the cache age, not just a generic 'Live rates' label — users making financial decisions need to know how fresh the data is

3

Use integer arithmetic or Dart's decimal package for currency math — never use double multiplication for monetary amounts

4

Handle per-currency decimal precision explicitly: JPY, KRW, VND = 0 decimal places; KWD, BHD, OMR = 3 decimal places; everything else = 2

5

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

6

Re-fetch rates when the app resumes from background after more than 1 hour — rates change during extended background sessions

7

Test the offline state explicitly: disable Wi-Fi and confirm cached rates display with a staleness banner, not a blank screen or crash

8

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

When You Need Custom Development

FlutterFlow covers standard rate-fetch-and-convert flows well. These scenarios require custom development:

  • Real-time tick-by-tick forex rates with sub-second latency for a trading or FX app — requires a WebSocket feed from a financial data provider (Polygon.io, Refinitiv), not a REST polling approach
  • Multi-currency checkout integration with rate locking at payment time — the rate shown at browse time must be locked when the user confirms payment, requiring a server-side rate reservation and expiry mechanism
  • Currency rate alerts with push notifications when a target rate is hit — requires background fetch, comparison logic, and push notification delivery via FCM/APNs beyond what FlutterFlow custom blocks can cleanly manage
  • Offline-first with background sync across devices — sqflite on-device plus Supabase sync when connectivity returns, including conflict resolution for favorite currencies edited on multiple devices

RapidDev builds these features for production

Real auth, database, and payments — fixed price, delivered in weeks. You own 100% of the code.

Get a fixed-price quote

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.

RapidDev

Need this feature production-ready?

RapidDev builds a currency converter into real apps — auth, database, payments — at $13K–$25K.

Book a free consultation

30-min call. No commitment.

Want this built for you?

We ship production apps at a fixed price — $13K–$25K, 6–10 weeks, source code yours. You've seen what it takes; we do it every week.

Get a fixed-price quote

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.