Friday 16 August 2024

Swift UI app to exchange currency using openexchangerates.org Api using MVVM

 

import SwiftUI

import Combine


// View

struct CurrencyConverterView1: View {

    @StateObject private var viewModel = CurrencyConverterView1Model()


    var body: some View {

        VStack {

            Picker("Select Currency", selection: $viewModel.selectedCurrency) {

                ForEach(viewModel.currencies, id: \.self) { currency in

                    Text(currency).tag(currency)

                }

            }

            .pickerStyle(MenuPickerStyle())

            

            TextField("Enter Amount", text: $viewModel.amount)

                .keyboardType(.decimalPad)

                .padding()

                .border(Color.gray)


            if let results = viewModel.convertedResults {// list view if data added

                List(results, id: \.currency) { result in

                    HStack {

                        Text(result.currency)

                        Spacer()

                        Text(String(format: "%.2f", result.amount))

                    }

                }

            }

        }

        .onAppear {

            viewModel.loadRates() // call api to fetch data 

        }

    }

}


// View Model

class CurrencyConverterView1Model: ObservableObject {

    @Published var currencies: [String] = []

    @Published var selectedCurrency: String = "USD"

    @Published var amount: String = "1"

    @Published var convertedResults: [ConversionResult1]?


    private var rates: [String: Double] = [:]

    private let dataService = ExchangeRatesService()


    func loadRates() {// Check if data outdated

        if dataService.isDataStale() { // last time check

            dataService.fetchRates { [weak self] rates in

                DispatchQueue.main.async {

                    self?.rates = rates

                    self?.currencies = Array(rates.keys)

                    self?.convertAmount()

                }

            }

        } else// Load from local storage

            rates = dataService.loadRatesFromStorage()

            currencies = Array(rates.keys)

            convertAmount()

        }

    }


    func convertAmount() {

        guard let inputAmount = Double(amount) else { return }

        convertedResults = rates.map { currency, rate in

            ConversionResult1(currency: currency, amount: inputAmount * rate)

        }

    }

}

//Model

struct ConversionResult1 {

    let currency: String

    let amount: Double

}


//Service class that handles fetching and local storage

class ExchangeRatesService {

    private let apiURL = "https://openexchangerates.org/api/latest.json"

    private let appID = "Get yOur own key from the website"

    private let lastFetchKey = "LastFetchDate"

    private let storageKey = "SavedRates"


    func fetchRates(completion: @escaping ([String: Double]) -> Void) {

        guard let url = URL(string: "\(apiURL)?app_id=\(appID)") else { return }

        

        URLSession.shared.dataTask(with: url) { data, response, error in

            if let data = data {

                if let json = try? JSONDecoder().decode(RatesResponse.self, from: data) {

                    self.saveRatesToStorage(json.rates)

                    self.saveLastFetchDate()

                    completion(json.rates)

                }

            }

        }.resume()

    }


    func isDataStale() -> Bool {// chekc if data is old

        if let lastFetch = UserDefaults.standard.object(forKey: lastFetchKey) as? Date {

            return Date().timeIntervalSince(lastFetch) > 1800 // 30 minutes  = 60*30secs

        }

        return true

    }


    func loadRatesFromStorage() -> [String: Double] { // load existing data from userdefults

        return UserDefaults.standard.dictionary(forKey: storageKey) as? [String: Double] ?? [:]

    }


    private func saveRatesToStorage(_ rates: [String: Double]) {

        UserDefaults.standard.set(rates, forKey: storageKey)

    }


    private func saveLastFetchDate() {

        UserDefaults.standard.set(Date(), forKey: lastFetchKey)

    }

}


struct RatesResponse: Codable {

    let rates: [String: Double]

}


#Preview {

    CurrencyConverterView1()

}

No comments:

Post a Comment