A Flutter app that displays exchange rates between currencies, with advanced state management and clean architecture.
This project adopts a Clean Architecture adapted to the Flutter context, where an MVVM-like (Model-View-ViewModel) approach and state management via BLoC/Cubit have been integrated:
- Model (
lib/data/): contains domain entities, data access logic, and data sources (API, local cache, etc.). - View (
lib/presentation/): includes the User Interface, which interacts with Bloc/Cubit and updates based on the application state. - ViewModel (
lib/business_logic/): implemented via Bloc/Cubit, acts as a mediator between View and Model, managing business logic and state.
This combination ensures a clear separation between presentation, business logic, and data access layers, guaranteeing testability, scalability, and maintainability.
Singleton Pattern
The classes CacheProvider, ExchangeRateProvider, and ExchangeRateRepository are implemented as Singletons.
This design choice ensures that only one instance of each provider/repository exists throughout the app's lifecycle, allowing:
- Efficient resource management (e.g., shared preferences, HTTP clients)
- Consistent access to cached data and API logic
- Avoidance of unnecessary object creation and memory usage
Singletons are especially useful in Flutter apps for services that must maintain a single state or cache, or that interact with device resources.
-
Startup and data loading
- On app launch, the
ApiCallsCubitcalls two repository functions, which in turn query both the backend (API) and the local cache. - For each request, it checks if the data is already present in the cache:
- If present, it reads from the cache.
- If absent, it downloads from the API:
- For exchange rates (
getConversionRates), the download occurs only once per day at the first app launch. - For the list of supported currencies (
getSupportedCodes), the download is one-time (static data, rarely changes).
- For exchange rates (
- On app launch, the
-
UI Initialization
- The "Amount" field is initially set to 1.
- The currency menu is set to USD - United States Dollar.
- The grid shows USD (value 1.00) in the first position, followed by all other currencies in alphabetical order.
-
Amount modification
- If the user only changes the amount, the values in the grid automatically update based on the new amount.
-
Reference currency modification
- If the user selects a different currency from the menu, the grid updates by showing the selected currency in the first position (with value 1.00) and all others in alphabetical order, with recalculated values.
-
Combined modification of amount and currency
- If the user changes both the amount and the currency, the grid updates by showing the selected currency in the first position and all others in alphabetical order, with values calculated based on the data received from the cache.
-
Value calculation
-
The calculation of exchange rates between currencies is handled entirely on the client side, with no further API calls after the first fetch. The logic is implemented in the private function
_calculateAndSortConversionRates: -
Logic explanation:
- It always starts from the downloaded and stored USD→X rates.
- When the user changes the reference currency or amount:
- It retrieves the USD→selected currency rate (
rateOfSelectedBaseToUSD). - For each currency X, it calculates the rate relative to the selected currency using the formula:
rate_selected_to_X = rate_USD_to_X / rate_USD_to_selected converted_value = rate_selected_to_X * amount
- It retrieves the USD→selected currency rate (
- The results are sorted by showing the selected currency first, followed by the others in alphabetical order.
- The state is updated via
emit, updating the UI reactively.
-
Reason for this choice:
- This solution allows avoiding additional API calls: all rates are calculated locally from already downloaded data.
- Consistency is ensured among the displayed rates, as they all derive from the same daily snapshot.
- The logic is simple, efficient, and easily extendable to any supported currency.
-
-
UI Management
- The UI automatically manages loading, success, and error states, displaying messages where necessary.
Un'app Flutter che mostra i tassi di cambio tra valute, con gestione avanzata dello stato e architettura pulita.
Questo progetto adotta una Clean Architecture adattata al contesto Flutter, dove è stato integrato un approccio MVVM-like (Model-View-ViewModel) e la gestione dello stato tramite BLoC/Cubit:
- Model (
lib/data/): contiene le entità di dominio, la logica di accesso ai dati e le fonti dati (API, cache locale, ecc.). - View (
lib/presentation/): comprende la User Interface, che interagisce con Bloc/Cubit e si aggiorna in base allo stato dell’applicazione. - ViewModel (
lib/business_logic/): realizzato tramite Bloc/Cubit, funge da mediatore tra View e Model, gestendo la logica di business e lo stato.
Questa combinazione garantisce una chiara separazione tra livelli di presentazione, logica di business e accesso ai dati, assicurando testabilità, scalabilità e manutenibilità.
Pattern Singleton
Le classi CacheProvider, ExchangeRateProvider ed ExchangeRateRepository sono implementate come Singleton.
Questa scelta progettuale garantisce che esista una sola istanza di ciascun provider/repository durante tutto il ciclo di vita dell’app, permettendo:
- Gestione efficiente delle risorse (es. shared preferences, HTTP client)
- Accesso coerente ai dati in cache e alla logica di API
- Evitare creazione inutile di oggetti e spreco di memoria
I Singleton sono particolarmente utili nelle app Flutter per servizi che devono mantenere uno stato unico o una cache, o che interagiscono con risorse di sistema.
-
Avvio e caricamento dati
- All’apertura dell’app, l’
ApiCallsCubitrichiama due funzioni del repository, che a loro volta interrogano sia il backend (API) sia la cache locale. - Per ogni richiesta verifica se i dati sono già presenti in cache:
- Se presenti, li legge dalla cache.
- Se assenti, li scarica dall’API:
- Per i tassi di cambio (
getConversionRates), il download avviene una sola volta al giorno alla prima apertura dell’app. - Per la lista delle valute supportate (
getSupportedCodes), il download è unico (dato statico, raramente cambia).
- Per i tassi di cambio (
- All’apertura dell’app, l’
-
Inizializzazione UI
- Il campo "Amount" è inizialmente impostato a 1.
- Il menù valuta è impostato su USD - United States Dollar.
- La griglia mostra al primo posto USD (valore 1.00), seguita da tutte le altre valute in ordine alfabetico.
-
Modifica dell’importo
- Se l’utente modifica solo l’amount, i valori nella griglia si aggiornano automaticamente in base al nuovo importo.
-
Modifica della valuta di riferimento
- Se l’utente seleziona una valuta diversa dal menù, la griglia si aggiorna mostrando la valuta scelta al primo posto (con valore 1.00) e tutte le altre in ordine alfabetico, con i valori ricalcolati.
-
Modifica combinata di amount e valuta
- Se l’utente modifica sia l’importo che la valuta, la griglia si aggiorna mostrando al primo posto la valuta selezionata e tutte le altre in ordine alfabetico, con i valori calcolati in base ai dati ricevuti dalla cache.
-
Calcolo dei valori
-
Il calcolo dei tassi di cambio tra valute è gestito interamente lato client, senza ulteriori chiamate API dopo il primo fetch. La logica è implementata nella funzione privata
_calculateAndSortConversionRates: -
Spiegazione della logica:
- Si parte sempre dai tassi USD→X scaricati e memorizzati.
- Quando l’utente cambia valuta di riferimento o amount:
- Si recupera il tasso USD→valuta selezionata (
rateOfSelectedBaseToUSD). - Per ogni valuta X, si calcola il tasso rispetto alla valuta selezionata con la formula:
rate_selected_to_X = rate_USD_to_X / rate_USD_to_selected valore_convertito = rate_selected_to_X * amount
- Si recupera il tasso USD→valuta selezionata (
- I risultati vengono ordinati mostrando la valuta selezionata al primo posto, seguita dalle altre in ordine alfabetico.
- Lo stato viene aggiornato tramite
emit, aggiornando la UI in modo reattivo.
-
Motivazione della scelta:
- Questa soluzione permette di evitare chiamate API aggiuntive: tutti i tassi sono calcolati localmente a partire dai dati già scaricati.
- Si garantisce consistenza tra i tassi mostrati, perché tutti derivano dallo stesso snapshot giornaliero.
- La logica è semplice, efficiente e facilmente estendibile a qualsiasi valuta supportata.
-
-
Gestione UI
- La UI gestisce automaticamente gli stati di loading, successo ed errore, mostrando messaggi dove necessario.