Skip to content

SergioPL14/minimaexchange

Repository files navigation

MinimaExchange

A minimalist, ultra-lightweight Android currency converter focused on speed, clean UI, and real-time exchange rates. Designed for offline-first use — ideal when travelling with limited connectivity.


Features

  • Real-time exchange rates — fetches live data from the ExchangeRate-API (161+ currencies, no API key required)
  • Offline support — rates are cached to disk and survive app restarts; the last known rates are always available even without a connection
  • Smart caching — in-memory cache with a 1-hour TTL; network is only hit when rates are stale
  • Cross-rate conversion — all pairs calculated via a stable EUR base, so any currency combination works
  • Searchable currency dropdown — tap the field and type to filter; keyboard opens immediately
  • Input validation — non-numeric input is flagged inline; stale results are cleared on each change
  • Dark mode — toggle in the app header; preference is persisted across sessions
  • "Rates updated X min ago" — timestamp shown below the result so you always know how fresh the data is
  • Swap button — swaps From/To currencies in one tap

Tech Stack

Layer Technology
Language Kotlin 2.x
UI Jetpack Compose + Material 3
Architecture MVVM (ViewModel + StateFlow)
Networking Ktor Client
Serialization kotlinx.serialization
Persistence SharedPreferences
Concurrency Kotlin Coroutines
Min SDK API 24 (Android 7.0)
Target SDK API 35 (Android 15)

Architecture

app/
└── src/main/java/com/minimaexchange/app/
    ├── MainActivity.kt                  # Single-activity entry point
    ├── data/
    │   ├── local/
    │   │   ├── AppPreferences.kt        # Dark mode persistence (SharedPreferences)
    │   │   └── RatesCache.kt            # Exchange rate disk cache (SharedPreferences + JSON)
    │   ├── model/
    │   │   └── ExchangeRateResponse.kt  # API response model
    │   └── remote/
    │       ├── ExchangeRateApi.kt       # Ktor HTTP client, fetches EUR-based rates
    │       └── HttpClientFactory.kt     # Ktor client setup with JSON content negotiation
    ├── domain/
    │   └── ConvertCurrencyUseCase.kt    # Caching logic, offline fallback, cross-rate calc
    └── ui/
        ├── screen/
        │   ├── ConverterScreen.kt       # Compose UI — amount input, dropdowns, result
        │   ├── ConverterViewModel.kt    # UI state, debounced auto-convert, dark mode toggle
        │   └── ConverterViewModelFactory.kt  # Manual DI — wires all dependencies
        └── theme/
            ├── Color.kt
            ├── Theme.kt                 # Light + dark Material 3 color schemes
            └── Type.kt

Data flow

User input
    └─> ConverterViewModel (StateFlow + debounce)
            └─> ConvertCurrencyUseCase
                    ├─> In-memory cache (< 1h old)  ──> Result
                    ├─> ExchangeRateApi (network)   ──> Result + save to RatesCache
                    └─> RatesCache (disk fallback)  ──> Result (offline)

Caching & Offline Behaviour

Scenario Behaviour
Fresh app, online Fetches rates, saves to disk
Rate cache < 1 hour old Returns in-memory cache, no network call
Rate cache > 1 hour old, online Fetches fresh rates, updates disk cache
Rate cache > 1 hour old, offline Falls back to any cached data (no age limit)
No cache at all, offline Shows "No internet connection" error

Building & Running

Prerequisites: Android Studio with JDK 21 (the project's gradle.properties points to the Android Studio bundled JDK).

# Debug build
./gradlew assembleDebug

# Install on connected device / emulator
./gradlew installDebug

# Run unit tests
./gradlew test

# Lint
./gradlew lint

API

Exchange rates are sourced from open.er-api.com — free tier, no API key, 161 currencies. All rates are fetched with EUR as the base and cross-rates are computed client-side:

result = amount × (toRate / fromRate)

About

Minimalist Android currency converter (Kotlin, Jetpack Compose, Ktor)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages