ios – Swift: How to Ensure current_price Which It Gets From API Updates Every Minute Using Timer?


“I’m encountering a challenge with my Swift app where I call the function getCryptoPrices upon pressing the ‘trackBtn,’ intending to fetch API updates every minute. However, I notice that the current_price only updates after the first minute, and subsequently, it refreshes every 11-12 minutes. I’ve set the timer for 1 minute, and I’d like to understand how I can ensure that the current_price updates every minute consistently. Any insights or suggestions on resolving this issue would be greatly appreciated. Here’s a snippet of my code for reference:

I have my model class:

   struct CryptoPrices: Decodable {
    var current_price: Double?
    mutating func update(current_price: Double) -> CryptoPrices {
        self.current_price = current_price
        return self
    }
}

CryptoManager class, where i store the business logic of the app:

    import Combine
    import Alamofire


class CryptoManager {
    static let shared = CryptoManager()
    var timer: Timer?
    private var cancellables: Set<AnyCancellable> = []
    var currentCryptoPrice: CryptoPrices?       

    func getCryptoPrices(symbol: String) -> AnyPublisher<CryptoPrices, Error> {
        let url = "https://example.com/api/v3/coins/markets?vs_currency=usd&ids=\(symbol)&order=market_cap_desc&per_page=100&page=1&sparkline=false&locale=en"

        var urlRequest = URLRequest(url: URL(string: url)!)
        urlRequest.httpMethod = HTTPMethod.get.rawValue
        urlRequest.cachePolicy = .reloadIgnoringLocalCacheData

        return AF.request(urlRequest)
            .publishDecodable(type: [CryptoPrices].self, decoder: JSONDecoder())
            .tryMap { response in
                guard let cryptoPricesArray = response.value else {
                    throw AFError.responseValidationFailed(reason: .dataFileNil)
                }

                // Extract the first element from the array
                guard let cryptoPrice = cryptoPricesArray.first else {
                    throw NSError(domain: "Invalid response format", code: 0, userInfo: nil)
                }

                if let updatedPrice = cryptoPrice.current_price {
                    return CryptoPrices(current_price: updatedPrice)
                } else {
                    return CryptoPrices(current_price: nil)
                }
            }
            .mapError { $0 as Error }
            .eraseToAnyPublisher()
    }
    
    func startFetchingWithTimer(symbol: String) -> AnyPublisher<CryptoPrices, Error> {
        // Create a Timer publisher that fires every 1 minutes
        let timerPublisher = Timer.publish(every: 60, on: .main, in: .common)
            .autoconnect()
            .map { _ in () }

        // Combine the timerPublisher with the fetchCryptoPrices publisher
        return timerPublisher
            .flatMap { _ in
                self.fetchCryptoPrices(symbol: symbol)
                    .handleEvents(receiveOutput: { cryptoPrices in
                        
                        print("Received crypto prices: \(cryptoPrices)")
                    })
            }
            .eraseToAnyPublisher()
    }

    private func fetchCryptoPrices(symbol: String) -> AnyPublisher<CryptoPrices, Error> {
        getCryptoPrices(symbol: symbol)
            .handleEvents(receiveCompletion: { completion in
                switch completion {
                case .failure(let error):
                    print("Error fetching crypto prices: \(error)")
                case .finished:
                    break
                }
            })
            .eraseToAnyPublisher()
    }

    private var subscriptions = Set<AnyCancellable>()

Here’s my controller:

import UserNotifications
import Combine

@IBOutlet weak var minimumRate: UITextField!
@IBOutlet weak var maximumRate: UITextField!
private let cryptoManager: CryptoManager
private var userNotificationCenter: UNUserNotificationCenter 
var symbol: String!
var timer : Timer?

 @IBAction func trackBtnPressed(_ sender: UIButton) {
    print("Track pressed")
    requestNotificationAuthorization()
    self.dismiss(animated: true)
}
 func startMonitoring(symbol: String, minRate: Double, maxRate: Double) {
    guard let minRateText = minimumRate.text, let maxRateText = maximumRate.text else {
        return
    }

    guard let minRate = Double(minRateText), let maxRate = Double(maxRateText) else {
        return
    }

    print("Minimum Rate: \(minRate), Maximum Rate: \(maxRate)")

    self.cryptoManager.startFetchingWithTimer(symbol: symbol)
        .sink(receiveCompletion: { completion in
            switch completion {
            case .failure(let error):
                print("Error fetching price: \(error)")
            case .finished:
                break
            }
        }, receiveValue: { cryptoPrices in
            // Now cryptoPrices is of type CryptoPrices
            if let currentPrice = cryptoPrices.current_price {
                print("Received price: \(currentPrice)")
                if currentPrice <= minRate {
                    print("Min rate:", minRate)
                    print(currentPrice, "after checking the price has dropped")
                    self.sendNotification(symbol: symbol, price: currentPrice)
                } else if currentPrice >= maxRate {
                    print("Max rate:", maxRate)
                    print(currentPrice, "after checking the price has increased")
                    self.sendNotification(symbol: symbol, price: currentPrice)
                } else {
                    print("Price is the same")
                }

            }
                })
                .store(in: &self.subscriptions)
}
    
    func sendNotification(symbol: String, price: Double) {
        let content = UNMutableNotificationContent()
        content.title = "The price of \(symbol) has changed"
        content.body = "Current price: \(price)$"
        
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5.0, repeats: false)
        
        let request = UNNotificationRequest(identifier: "price_alert", content: content, trigger: trigger)
        
        userNotificationCenter.add(request) { error in
            if let error = error {
                print(error)
            }
        }
    }
    private var subscriptions = Set<AnyCancellable>()
    
    func requestNotificationAuthorization() {
        let notificationCenter = UNUserNotificationCenter.current()
        notificationCenter.getNotificationSettings { settings in
            switch settings.authorizationStatus {
            case .notDetermined:
                notificationCenter.requestAuthorization(options: [.alert, .sound]) { didAllow, error in
                    if didAllow {
                        self.startMonitoring(
                            symbol: self.symbol ?? "Not working",
                            minRate: Double(self.minimumRate.text(in: self.minimumRate.selectedTextRange!) ?? "") ?? 0.0,
                            maxRate: Double(self.maximumRate.text(in: self.maximumRate.selectedTextRange!) ?? "") ?? 0.0
                        )
                    }
                }
            case .denied:
                return
            case .authorized:
                DispatchQueue.main.async {
                    self.startMonitoring(
                        symbol: self.symbol ?? "Not working",
                        minRate: Double(self.minimumRate.text(in: self.minimumRate.selectedTextRange!) ?? "") ?? 0.0,
                        maxRate: Double(self.maximumRate.text(in: self.maximumRate.selectedTextRange!) ?? "") ?? 0.0
                    )
                }
                
            default:
                return
            }
        }
    }

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img