2

I need to implement a not too complicated use case in SwiftUI: Login with username and password, authToken and refreshToken are come back from API, store the refreshToken in KeyChain and the authToken in AppState (which is an ObservableObject (environmentObject), code below). If any of my requests come back with 401 HTTP response code (so the accessToken is expired), I need to refresh the accessToken and retry my original request with the new token. But the problem is I call the RefreshTokenRequest from my ViewModel, and I cannot pass token "back" to the appState through the View. So the question is, how can I access my environmentObject from my ViewModel? Is it possible? Here's my code simplified at playground file:

class AppState: ObservableObject {
    @Published var isLoggedIn: Bool = false
    @Published var token: String = ""
}

struct ProtectedView: View {
    @EnvironmentObject var appState: AppState

    var body: some View {
        VStack {
            RollerListView(token: self.appState.token)
        }
    }
}

struct Roller: Codable, Hashable {
    var scooter_name: String;
}

class RollerListVM: ObservableObject {
    @Published var rollers = [Roller]()

    func getRollers(token: String) {
        print("FETCHING ROLLERS...")
        /// Here's my API request to get the array of rollers. If the token is expired, I need to refresh it with a new request and I need to pass the new token to the appState.token variable, because I need to use the new token in all of my request instead of expired one.
        /// IF THE TOKEN IS REFRESHED, HOW CAN I PASS IT TO THE AppState.token ???
        DispatchQueue.main.async {
            self.rollers = [Roller(scooter_name: "scooter 1"), Roller(scooter_name: "scooter 2")]
        }
    }

    init(token: String) {
        self.getRollers(token: token)
    }
}

struct RollerListView: View {
    @ObservedObject private var rollerListVM: RollerListVM;
    @EnvironmentObject var appState: AppState

    var body: some View {
        List(rollerListVM.rollers, id: \.self) { roller in
            HStack {
                Text(roller.scooter_name)
            }
        }
    }

    init(token: String) {
        self.rollerListVM = RollerListVM(token: token)
    }
}

I tried to pass a closure function to the ViewModel and call it if after token refresh (with the new token as a parameter), but it's not working, because the self parameter is mutating inside the closure.

How should I do that?

2
  • 1
    Don't initialise RollerListVM etc with the token string, rather pass the AppState object and have the view model obtain the token from that as needed. If the token is expired, call a refreshToken method on the AppState object so that it updates its token property Commented Mar 2, 2020 at 19:39
  • Well, I red too much docs and follow too much tutorials until now, I just missed this obvious solution. Thank you sir. Please post it than I can accept as a solution. Commented Mar 2, 2020 at 20:57

1 Answer 1

4

Since your token is somewhat dynamic, I would suggest that you shouldn't pass it directly to your view models. Rather, pass the AppState object and retrieve the token when needed.

If you detect an expired token you can call a function on the AppState that obtains a refresh token and updates its token property.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.