0

I would like to create a module, that will refresh token if needed and repeat latest request using Combine from Apple.

For now, every part is works well, but not this one:

  public func executeRequest<T: Decodable, E: ServerErrorType>(
    _ request: HTTPRequest,
    mapper: ObjectMapper<T, E>
  ) -> AnyPublisher<(T, HTTPResponse), Error> {
        return authentificator // <- handle refresh token stuff
          .refreshToken(force: false)
          .subscribe(on: DispatchQueue.global())
          .flatMap { token in // <- on obtain token - transform it into request

---> (here)
            session.publisher(for: request, mapper: mapper, token: token) // <- create request 
---> (here)
              .tryCatch({ error -> AnyPublisher<(T, HTTPResponse), Error> in
                if let error = error as? ServerErrorType,
                   error.isAuthError {
                  
                  return authentificator
                    .refreshToken(force: true)
                    .subscribe(on: DispatchQueue.global())
                    .flatMap { token -> AnyPublisher<(T, HTTPResponse), Error> in
                      session.publisher(for: request, mapper: mapper, token: token) //<- repeat if token refreshed
                    }
                    .eraseToAnyPublisher()
                } else {
                  throw error
                }
              })
              .print()
          }
          .receive(on: DispatchQueue.main)
          .print()
          .eraseToAnyPublisher()
   }

In place where I mark (here) when I have expired token, tryCatch wont works, instead "received canceled" printed in console. I'm not sure what I did wrong. Can any on advice?

hbk
  • 10,908
  • 11
  • 91
  • 124
  • How are you subscribing to `executeRequest`? Are you storing the `AnyCancellable`? Also, probably unrelated, but is there a reason you need `.subscribe(on: DispatchQueue.global())`? – New Dev Aug 01 '21 at 02:18
  • @NewDev, yes, i store cancellable after sink, and yes - subscribe(on:) can be omitted – hbk Aug 01 '21 at 05:56

1 Answers1

0

I found the reason - my set with AnyCancellable is become nil because ViewModel become nil due to root TabBar reinit because of SwiftUI update process. (I have a TabBar as an OptionalView in another View that is root)

  var body: some View {
    VStack {
      switch viewModel.currentFlow {
        case .onboarding:
          WelcomeView()
            .transition(.opacity)
        case .main:
          MainTabBarView() // <- reinit here cause the issue
            .transition(.opacity)
     }
    }
  }

The code is completely correct for refresh token, and the possible fix is:

  • either to use @StateObject for ViewModel
@ObservedObject private var viewModel: <MyViewModel>

update to:

@StateObject private var viewModel: <MyViewModel>
  • either to use same ☝️ instance of TabBar at any reload.

possible fix:

  private lazy let mainTabBar: MainTabBarView = .init() // <- init only once
  private lazy let welcome: WelcomeView = .init()
  
  var body: some View {
    VStack {
      switch viewModel.currentFlow {
        case .onboarding:
          welcome
            .transition(.opacity)
        case .main:
          mainTabBar
            .transition(.opacity)
      }
    }
  }

hbk
  • 10,908
  • 11
  • 91
  • 124