QuickBirdEng / XCoordinator

šŸŽŒ Powerful navigation library for iOS based on the coordinator pattern
MIT License
2.26k stars 177 forks source link

Initial or pre-used cordinators could not be dismissed #188

Closed berkakkerman closed 3 years ago

berkakkerman commented 4 years ago

I am trying to implemented an app with sign in and sign out flow. Firstly, I have main coordinator(AppCoordinator) like below. AppCoordinator routes to AuthCoordinator first and AuthCoordinator has 3 views;

AuthRoute.login -> AuthRoute.setupName -> AuthRoute.addAccount

After a successful sign in, App routes to HomeCoordinator with a publish subject subscription. HomeCoordinator is like TabBarCoordinator. In the Profile tab, a sign out button action is available.

HomeRoute

After logout

.multiple(.dismissAll(), .presentFullScreen(self.authCoordinator, animation: .default))

Although I did .dismissAll() first, the old AuthCoordinator is opening. AuthCoordinator keeps its state. How can I re-create the AuthCoordinator and start it again from a new AuthCoordinator. This issue is happening for the HomeCoordinator.

AppCoordinator is created by a function from AppDelegate.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { setupCoordinator() return true }

AppCoordinator is instantiating with Swinject here.

private func setupCoordinator() { self.appCoordinator = appAssembler.resolver.resolve(AppCoordinator.self)! let router = self.appCoordinator.strongRouter router.setRoot(for: mainWindow) }

AppCoordinator with 3 routes.

class AppCoordinator: NavigationCoordinator<AppRoute> {

    private let homeCoordinator: HomeCoordinator
    private var authCoordinator: AuthCoordinator
    private let introCoordinator: IntroCoordinator

    init(homeCoordinator: HomeCoordinator,
         authCoordinator: AuthCoordinator,
         introCoordinator: IntroCoordinator ) {
        self.authCoordinator = authCoordinator        
        self.homeCoordinator = homeCoordinator
        self.introCoordinator = introCoordinator
        var initialRoute: AppRoute = .intro

        if userDefaultService.isShowIntro {
            if sessionService.isTokenValid {
                initialRoute = .home
            } else {
                initialRoute = .auth
            }
        }

        super.init(initialRoute: initialRoute)
        self.subscribeToSessionChanges()
    }

    // MARK: - Overrides
    override func prepareTransition(for route: AppRoute) -> NavigationTransition {

        switch route {
        case .auth:
            return .multiple(.dismissAll(), .presentFullScreen(self.authCoordinator, animation: .default))
        case .home:
            return .multiple(.dismissAll(), .presentFullScreen(self.homeCoordinator, animation: .default))
        case .intro:
            return .multiple(.dismissAll(), .presentFullScreen(self.introCoordinator, animation: .default))
        }
    }
private func subscribeToSessionChanges() {

        self.sessionService.didSignIn
            .subscribe(onNext: { [weak self] _ in
                self?.showHome()
            })
            .disposed(by: self.disposeBag)

        self.sessionService.didSignOut
            .subscribe(onNext: { [weak self] _ in
                self?.showAuth()
            })
            .disposed(by: self.disposeBag)
    }
enum AuthRoute: Route {
    case login
    case setupName(SetupNameModel)
    case setupAccount(AddAccountModel)
    case signOut
}
class AuthCoordinator: NavigationCoordinator<AuthRoute> {

    // MARK: - Stored properties

    // MARK: Initialization

    init(initialRoute: AuthRoute = .login) {
        super.init(initialRoute: initialRoute)
    }

    // MARK: Overrides

    override func prepareTransition(for route: AuthRoute) -> NavigationTransition {
        switch route {
        case .login:
            let viewController = ModuleBuilder<LoginVC>.buildModule(coordinator: unownedRouter)
            return .push(viewController)
            return .push(viewController)
        case .setupName(let model):
            let viewController = ModuleBuilder<SetupNameVC>.buildModule(context: model, coordinator: unownedRouter)
            return .push(viewController)
        case .setupAccount(let model):
            let viewController = ModuleBuilder<AddAccountVC>.buildModule(context: model, coordinator: unownedRouter)
            return .push(viewController)
        case .signOut:
            self.rootViewController.removeFromParent()
            return .multiple(.popToRoot(), .dismissAll(), .dismissToRoot(), .dismiss())
        }
    }
}
class AuthCoordinator: NavigationCoordinator<AuthRoute> {

    // MARK: - Stored properties

    // MARK: Initialization

    init(initialRoute: AuthRoute = .login) {
        super.init(initialRoute: initialRoute)
    }

    // MARK: Overrides

    override func prepareTransition(for route: AuthRoute) -> NavigationTransition {
        switch route {
        case .login:
            let viewController = ModuleBuilder<LoginVC>.buildModule(coordinator: unownedRouter)
            return .push(viewController)
            return .push(viewController)
        case .setupName(let model):
            let viewController = ModuleBuilder<SetupNameVC>.buildModule(context: model, coordinator: unownedRouter)
            return .push(viewController)
        case .setupAccount(let model):
            let viewController = ModuleBuilder<AddAccountVC>.buildModule(context: model, coordinator: unownedRouter)
            return .push(viewController)
        case .signOut:
            self.rootViewController.removeFromParent()
            return .multiple(.popToRoot(), .dismissAll(), .dismissToRoot(), .dismiss())
        }
    }
}
class HomeCoordinator: TabBarCoordinator<HomeRoute> {

    // MARK: Stored properties

    private let historyRouter: StrongRouter<HistoryRoute>
    private let paymentRouter: StrongRouter<PaymentRoute>
    private let profileRouter: StrongRouter<ProfileRoute>

    // MARK: Initialization

    init(historyCoordinator: HistoryCoordinator,
         paymentCoordinator: PaymentCoordinator,
         profileCoordinator: ProfileCoordinator) {

        self.historyRouter = historyCoordinator.strongRouter
        self.paymentRouter = paymentCoordinator.strongRouter
        self.profileRouter = profileCoordinator.strongRouter

        let homeVC = ModuleBuilder<HomeVC>.buildModule()
        super.init(rootViewController: homeVC, tabs: [historyRouter, paymentRouter, profileRouter], select: paymentRouter)
    }

    // MARK: Overrides

    override func prepareTransition(for route: HomeRoute) -> TabBarTransition {
        switch route {
        case .history:
            return .select(historyRouter)
        case .payment:
            return .select(paymentRouter)
        case .profile:
            return .select(profileRouter)
        }
    }
}
enum ProfileRoute: Route {
    case logout
}
class ProfileCoordinator: NavigationCoordinator<ProfileRoute> {

    // MARK: Initialization

    let authCoordinator: AuthCoordinator

    init(authCoordinator: AuthCoordinator) {
        self.authCoordinator = authCoordinator
        super.init(initialRoute: .profile)
    }

    // MARK: Overrides    
    override func prepareTransition(for route: ProfileRoute) -> NavigationTransition {
        switch route {
        case .logout:
            return .multiple(.dismissAll(), .dismiss())
        }
    }
}
mathemandy commented 4 years ago

@berkakkerman any solution to this ?

berkakkerman commented 4 years ago

@mathemandy Unfortunately, not yet :(

pauljohanneskraft commented 4 years ago

Hey,

each coordinator has a rootViewController (e.g. a UINavigationController in the case of a NavigationCoordinator), which it holds strongly. To make sure, that each coordinator is cleaned, you will have to trigger a route on the AuthCoordinator that is removing the existing state - or: can you simply create a new instance instead?

berkakkerman commented 4 years ago

@pauljohanneskraft Thank you. It works like a charm right now.