EZRate
Sign in

Flutter Currency Tracker Tutorial (plus React Native): Build an Iranian Gold and FX App

A Flutter currency tracker tutorial for live Iranian dollar, gold and coin rates: backend proxy, models, polling, offline cache, RTL and Persian digits.

Arian Asadi
· Writer and Market Analyst
Published 7 November 2026, 20:00 · Updated 09:24 · 4 min read
Flutter Currency Tracker Tutorial (plus React Native)
Key takeaways
  • The app talks to your own backend (which holds the EZRate key), never to the API directly.
  • A small repository class polls once a minute while the app is in the foreground and caches the last response for offline use.
  • RTL layout, Persian digits and clear units make the app feel native to Iranian users.

A rate tracker is one of the most downloaded app categories in Iran. This Flutter currency tracker tutorial builds a clean, production-minded app for live Tehran dollar, gold and coin prices, and shows the React Native equivalent of the key parts.

i
EZRate API
This guide uses the EZRate currency and gold price API: full documentation, code samples and a free key are on the API page.
★
Short answer
Expose /rates from your backend (proxying https://api.ezrate.me/v1/rates with your key and a 60-second cache). In Flutter, fetch it with http, parse into a Rate model, poll every minute only while the app is resumed, store the last response with shared_preferences for offline start, and format with intl for Persian digits and RTL.

Part of the Iranian Rial exchange rate API guide. Persian readers: Flutter currency app.

1. Backend first

Do not embed the key in the app; binaries can be decompiled. Use the proxy from securing financial API keys and point the app at https://your-backend.example/rates.

2. Model

dart
class Rate {
  final String symbol, name;
  final int buy, sell;
  final double changePct;
  final bool lowSources;
  final DateTime updatedAt;

  Rate.fromJson(Map<String, dynamic> j)
      : symbol = j["symbol"],
        name = j["name"],
        buy = (j["buy"] as num).round(),
        sell = (j["sell"] as num).round(),
        changePct = (j["change_pct"] as num).toDouble(),
        lowSources = j["low_sources"] == true,
        updatedAt = DateTime.parse(j["updated_at"]);
}

3. Repository with offline cache

dart
import "dart:convert";
import "package:http/http.dart" as http;
import "package:shared_preferences/shared_preferences.dart";

class RatesRepo {
  static const _url = "https://your-backend.example/rates";
  static const _key = "rates_cache";

  Future<List<Rate>> cached() async {
    final s = (await SharedPreferences.getInstance()).getString(_key);
    return s == null ? [] : _parse(s);
  }

  Future<List<Rate>> fetch() async {
    final r = await http.get(Uri.parse(_url)).timeout(const Duration(seconds: 10));
    if (r.statusCode != 200) throw Exception("HTTP ${r.statusCode}");
    (await SharedPreferences.getInstance()).setString(_key, r.body);
    return _parse(r.body);
  }

  List<Rate> _parse(String body) =>
      (jsonDecode(body)["data"] as List).map((e) => Rate.fromJson(e)).toList();
}

4. Lifecycle-aware polling

dart
class RatesController extends ChangeNotifier with WidgetsBindingObserver {
  final repo = RatesRepo();
  List<Rate> rates = [];
  bool stale = false;
  Timer? _t;

  Future<void> start() async {
    WidgetsBinding.instance.addObserver(this);
    rates = await repo.cached();
    notifyListeners();
    _tick();
    _t = Timer.periodic(const Duration(minutes: 1), (_) => _tick());
  }

  Future<void> _tick() async {
    try { rates = await repo.fetch(); stale = false; } catch (_) { stale = true; }
    notifyListeners();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState s) {
    if (s == AppLifecycleState.resumed) { _tick(); _t ??= Timer.periodic(const Duration(minutes: 1), (_) => _tick()); }
    if (s == AppLifecycleState.paused) { _t?.cancel(); _t = null; }
  }
}

Stopping the timer in the background saves battery and requests. For home-screen widgets and push alerts you need platform services; EZRate’s own mobile app is an example of the result.

5. UI: RTL, digits and units

dart
final fa = NumberFormat.decimalPattern("fa");

ListTile(
  title: Text(rate.name),
  subtitle: Text(rate.lowSources ? "کم‌منبع" : ""),
  trailing: Directionality(
    textDirection: TextDirection.ltr,
    child: Text("${fa.format(rate.sell)} تومان",
        style: TextStyle(color: rate.changePct >= 0 ? Colors.green : Colors.red)),
  ),
);
  • Set locale: Locale("fa") and supportedLocales; Flutter flips layouts automatically.
  • Wrap numbers in LTR Directionality so signs and separators stay in place.
  • Always print the unit; see IRR vs Toman.

React Native equivalent

tsx
import { useEffect, useState } from "react";
import { AppState, FlatList, Text } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";

export function useRates() {
  const [rates, setRates] = useState<any[]>([]);
  useEffect(() => {
    let timer: any;
    const load = async () => {
      try {
        const r = await fetch("https://your-backend.example/rates");
        const body = await r.text();
        await AsyncStorage.setItem("rates", body);
        setRates(JSON.parse(body).data);
      } catch {}
    };
    AsyncStorage.getItem("rates").then(s => s && setRates(JSON.parse(s).data));
    const start = () => { load(); timer = setInterval(load, 60_000); };
    start();
    const sub = AppState.addEventListener("change", s => { clearInterval(timer); if (s === "active") start(); });
    return () => { clearInterval(timer); sub.remove(); };
  }, []);
  return rates;
}
US Dollar265,200Toman−0.46%

Architecture of the app

The app is deliberately thin. Your backend talks to EZRate, caches rates for a minute and exposes a small JSON endpoint. The repository class in the app fetches that endpoint and stores the last response on the device. The controller owns the refresh timer and pauses it in the background. The widgets only render what the controller exposes. This separation makes each layer testable: you can unit-test parsing with a fixture, test the controller with a fake repository, and run widget tests without any network.

Going further

  • Favourites: let users pin assets; store the list locally and request only those symbols.
  • Charts: add /history/{symbol} through your backend and draw with fl_chart (Flutter) or victory-native (React Native).
  • Price alerts: run the alert logic on your server and deliver via FCM/APNs; polling on the device is unreliable in the background.
  • Home-screen widgets: need native code (Android App Widgets, iOS WidgetKit) that reads the cached snapshot.
  • Store review: state the data source and update time in the app and in the listing to avoid “misleading data” rejections.

Persian readers have a step-by-step Flutter-only guide in Flutter currency app.

Performance and battery

Rate apps are opened many times a day for a few seconds, so start-up time matters more than anything else. Render the cached snapshot immediately and refresh in the background; the user sees numbers at once and they update a moment later. Keep the payload small by requesting only the symbols on screen, and avoid timers in the background, which both drain the battery and are killed by the operating system anyway. On Android, test with battery saver on; on iOS, test after the app has been suspended for a while.

Finally, design for poor connections. Show the time of the cached data clearly, retry with backoff when the network returns, and never replace good cached values with an empty list after a failed request.

Related live rates: US dollar rate, 18K gold price, Emami coin price.

Can a mobile app call the EZRate API directly?
It should not hold the key; route requests through your backend proxy.
How often should the app refresh?
Once a minute while in the foreground; stop polling in the background.
How do I show Persian numbers in Flutter?
Use NumberFormat from the intl package with the fa locale.
Does the app work offline?
With the cached last response it can show the latest known rates and a stale notice.
Arian Asadi
I studied economics and developed an interest in financial markets, exchange rates, and economic developments during my university years. Throughout my professional career, I have focused particularly on foreign exchange markets, gold, international money transfers, and the impact of economic developments on prices. In the articles I write, I strive to present information about exchange rates and financial markets in a clear and accessible way, based on verifiable data and market trends. My goal is to help readers gain a clearer understanding of market conditions before making decisions about buying, selling, or transferring currencies.
All posts by author →

Comments (0)

Comments are shown after review. Sign-in is needed to send.

Related

API & Developers
React Currency Converter for Live Toman Rates, with a Secure Backend Proxy
API & Developers
Securing Financial API Keys in Web and Mobile Apps: Proxies, Restrictions and Rotation
API & Developers
Laravel Exchange Rate API Tutorial: Live Iranian Rates with Http Client, Redis Cache and a Scheduler