Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How should I trigger network call in SwiftUI app to refresh data on app open?

Tags:

ios

swiftui

I'm writing a SwiftUI app, and I want it periodically refresh data from a server:

  • when the app is first opened
  • if the app enters the foreground and the data has not been updated in the past 5 minutes

Below is the code I have so far.

What is the best way to trigger this update code the first time the app is opened in a SwiftUI app? Is adding the observer in onAppear a good practice for triggering the update when the app enters the foreground? (This is the only view in the app)

class InfoStore {

    var lastValueCheck: Date = .distantPast
}

struct ContentView : View {

    var infoStore: InfoStore

    private func updateValueFromServer() {

        // request updated value from the server

        // if the request is successful, store the new value

        currentValue = 500
        UserDefaults.cachedValue = 500
        // hardcoded for this example

        infoStore.lastValueCheck = Date()
    }

    private func updateValueIfOld() {

        let fiveMinutesAgo: Date = Date(timeIntervalSinceNow: (-5 * 60))

        if infoStore.lastValueCheck < fiveMinutesAgo {
            updateValueFromServer()
        }
    }

    @State var currentValue: Int = 100

    var body: some View {
        Text("\(currentValue)")
        .font(.largeTitle)
        .onAppear {
                NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification,
                                                       object: nil,
                                                       queue: .main) { (notification) in
                    self.updateValueIfOld()
                }
        }
    }
}

extension UserDefaults {

    private struct Keys {
        static let cachedValue = "cachedValue"
    }

    static var cachedValue: Int {
        get {
            return standard.value(forKey: Keys.cachedValue) as? Int ?? 0
        }
        set {
            standard.set(newValue, forKey: Keys.cachedValue)
        }
    }
}
like image 587
gohnjanotis Avatar asked Dec 27 '25 14:12

gohnjanotis


1 Answers

1) About the first point (app first opened): probably the best way to get what you want is to extract the logic outside the View (as MVVM suggests) using DataBinding and ObservableObjects. I changed your code as less as possible in order to show you what I mean:

import SwiftUI

class ViewModel: ObservableObject {
    @Published var currentValue = -1
    private var lastValueCheck = Date.distantPast

    init() {
        updateValueFromServer()
    }

    func updateValueIfOld() {
        let fiveMinutesAgo: Date = Date(timeIntervalSinceNow: (-5 * 60))

        if lastValueCheck < fiveMinutesAgo {
            updateValueFromServer()
        }
    }

    private func updateValueFromServer() {
        // request updated value from the server

        // if the request is successful, store the new value

        currentValue = 500
        UserDefaults.cachedValue = 500
        // hardcoded for this example

        lastValueCheck = Date()
    }
}

struct ContentView : View {

    @ObservedObject var viewModel: ViewModel

    var body: some View {
        Text("\(viewModel.currentValue)")
        .font(.largeTitle)
        .onAppear {
                NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification,
                                                       object: nil,
                                                       queue: .main) { (notification) in
                                                        self.viewModel.updateValueIfOld()
                }
        }
    }
}

extension UserDefaults {

    private struct Keys {
        static let cachedValue = "cachedValue"
    }

    static var cachedValue: Int {
        get {
            return standard.value(forKey: Keys.cachedValue) as? Int ?? 0
        }
        set {
            standard.set(newValue, forKey: Keys.cachedValue)
        }
    }
}

#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(viewModel: ViewModel())
    }
}
#endif

This way, as soon as the ViewModel is created the currentValue is updated. Also, every time currentValue is changed by a server call the UI is automatically recreated for you. Note that you have to modify the sceneDelegate this way:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: ContentView(viewModel: ViewModel()))
            self.window = window
            window.makeKeyAndVisible()
        }
    }

2) About the second point (app enters foreground): you should be careful here because you're registering the observer multiple times (every time the onAppear is fired). Depending on your needs you should decide to:

  • remove the observer onDisappear (this is very frequent)
  • add the observer just one time checking if you have already added it.

In any case it's a good practice to implement the:

deinit {

}

method and eventually remove the observer.

like image 75
matteopuc Avatar answered Dec 30 '25 03:12

matteopuc



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!