Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Core] RxFlow로 코디네이터의 기본적인 구조를 설계했어요. #208

Open
wants to merge 9 commits into
base: feature/myeongsoo/add_rxFlow
Choose a base branch
from
1 change: 1 addition & 0 deletions Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def shared_pods
pod 'RxViewController'
pod 'SwiftLinkPreview', '~> 3.4.0'
pod 'UITextView+Placeholder', '~> 1.2'
pod 'RxFlow'
end

target 'Tooda' do
Expand Down
74 changes: 74 additions & 0 deletions Tooda/Sources/Core/Coordinator/Flows/App/AppFlow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// AppFlow.swift
// Tooda
//
// Created by Lyine on 2022/03/26.
//

import UIKit
import RxFlow
import RxSwift
import RxCocoa

// Flow는 AnyObject를 준수하므로 class로 선언해주어야 합니다!
final class AppFlow: Flow {
var root: Presentable {
return self.rootWindow
}

struct Dependency {
let appInject: AppInjectRegister & AppInjectResolve
}

private let rootWindow: UIWindow
private let dependency: Dependency

init(
with window: UIWindow,
dependency: Dependency
) {
self.rootWindow = window
self.dependency = dependency
}

deinit {
print("\(type(of: self)): \(#function))")
}

func navigate(to step: Step) -> FlowContributors {
guard let step = step.asToodaStep else { return .none }

switch step {
case .loginIsRequired:
return coordinateToLogin()

case .loginIsCompleted, .homeIsRequired:
return coordinateToHome()

default:
return .none
}
}

private func coordinateToLogin() -> FlowContributors {
let loginFlow = LoginFlow(with: .init(appInject: self.dependency.appInject))

Flows.use(loginFlow, when: .created) { [unowned self] root in
self.rootWindow.rootViewController = root
}

let nextStep = OneStepper(withSingleStep: ToodaStep.loginIsRequired)

return .one(
flowContributor: .contribute(
withNextPresentable: loginFlow,
withNextStepper: nextStep
)
)
}

// TODO: HomeFlow를 연결할 예정이에요.
private func coordinateToHome() -> FlowContributors {
return .none
}
}
70 changes: 70 additions & 0 deletions Tooda/Sources/Core/Coordinator/Flows/Login/LoginFlow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// LoginFlow.swift
// Tooda
//
// Created by Lyine on 2022/03/26.
//

import UIKit
import RxFlow

final class LoginFlow: Flow {

var root: Presentable {
return self.rootViewController
}

private let rootViewController: UINavigationController = .init()

struct Dependency {
let appInject: AppInjectRegister & AppInjectResolve
}

private let dependency: Dependency


init(with dependency: Dependency) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

공유주신대로 dependency를 가져야 되는 군요ㅠ ㅎㅎ 다른 대안이 저도 떠오르지 않아서 이렇게 구성해야 할듯요

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네네 Flow에서 ViewController를 보내는 형태가 되어서 생성시점에 의존성을 넘겨주려면 Flow가 프로퍼티로 들고있어야겠더라구요..

self.dependency = dependency
}

deinit {
print("\(type(of: self)): \(#function)")
}

func navigate(to step: Step) -> FlowContributors {
guard let step = step.asToodaStep else { return .none }

switch step {
case .loginIsRequired:
return coordinateToLogin()
case .loginIsCompleted:
return .end(forwardToParentFlowWithStep: ToodaStep.homeIsRequired)
default:
return .none
}

}
}

// MARK: - Extensions

extension LoginFlow {
private func coordinateToLogin() -> FlowContributors {
let reactor = LoginReactor(
dependency: .init(
service: self.dependency.appInject.resolve(NetworkingProtocol.self),
coordinator: self.dependency.appInject.resolve(AppCoordinatorType.self),
localPersistanceManager: self.dependency.appInject.resolve(LocalPersistanceManagerType.self),
socialLoginService: self.dependency.appInject.resolve(SocialLoginServiceType.self),
snackBarEventBus: SnackBarEventBus.event.asObservable()
)
)

let viewController = LoginViewController(reactor: reactor)

self.rootViewController.pushViewController(viewController, animated: true)

return .one(flowContributor: .contribute(withNextPresentable: viewController,
withNextStepper: reactor))
}
}
36 changes: 36 additions & 0 deletions Tooda/Sources/Core/Coordinator/Steps/AppStepper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// AppStepper.swift
// Tooda
//
// Created by Lyine on 2022/03/26.
//

import Foundation
import RxFlow
import RxSwift
import RxRelay

struct AppStepper: Stepper {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

struct로 한 이유가 어떤거에요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

샘플로 찾아본 예제에서 struct로 설계했어서 크게 고려하지 않았었습니다.
RxFlowDemo를 보니 class로 사용하고 있네요. Stepper의 기능적인 면으로 봤을 때 클래스로 사용하는것이 바람직해보입니다!
참고
피드백 주셔서 감사합니다!!

let steps: PublishRelay<Step> = .init()

struct Dependency {
let persistenceManager: LocalPersistanceManagerType
}

private let dependency: Dependency
private let disposeBag: DisposeBag = .init()

init(dependency: Dependency) {
self.dependency = dependency
}

func readyToEmitSteps() {
let token: AppToken? = self.dependency.persistenceManager.objectValue(forKey: .appToken)

Observable.just(token)
.map { $0 != nil }
.map { $0 ? ToodaStep.loginIsCompleted : ToodaStep.loginIsRequired }
.bind(to: steps)
.disposed(by: self.disposeBag)
}
}
16 changes: 16 additions & 0 deletions Tooda/Sources/Core/Coordinator/Steps/Step+asSample.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// Step+asSample.swift
// Tooda
//
// Created by Lyine on 2022/03/26.
//

import Foundation

import RxFlow

extension Step {
var asToodaStep: ToodaStep? {
return self as? ToodaStep
}
}
22 changes: 22 additions & 0 deletions Tooda/Sources/Core/Coordinator/Steps/ToodaStep.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// ToodaStep.swift
// Tooda
//
// Created by Lyine on 2022/03/26.
//

import Foundation
import RxFlow

enum ToodaStep: Step {
// Global
case alert(message: String)
case dismiss

// Login
case loginIsRequired
case loginIsCompleted

// Home
case homeIsRequired
}
17 changes: 13 additions & 4 deletions Tooda/Sources/Scenes/Login/LoginReactor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ import Foundation
import ReactorKit
import RxSwift
import RxCocoa
import Firebase
import RxFlow
import FirebaseAnalytics
import Then

final class LoginReactor: Reactor {
final class LoginReactor: Reactor, Stepper {

// MARK: - Constants

struct Dependency {
let service: NetworkingProtocol
@available(*, deprecated, message: "RxFlow로 대체될 예정이에요.")
let coordinator: AppCoordinatorType
let localPersistanceManager: LocalPersistanceManagerType
let socialLoginService: SocialLoginServiceType
Expand Down Expand Up @@ -48,6 +50,9 @@ final class LoginReactor: Reactor {

let initialState: State = State(isAuthorized: false)

// MARK: - Stepper
var steps: PublishRelay<Step> = .init()

// MARK: - Con(De)structor

init(dependency: Dependency) {
Expand Down Expand Up @@ -110,8 +115,8 @@ extension LoginReactor {
}

private func routeToHomeMutation() -> Observable<Mutation> {
FirebaseAnalytics.Analytics.logEvent(
AnalyticsEventLogin,
Analytics.logEvent(
AnalyticsEventLogin,
parameters: [
AnalyticsParameterSuccess: 1
]
Expand All @@ -122,6 +127,10 @@ extension LoginReactor {
shouldNavigationWrapped: true
)


// TODO: 화면 연결이 끝나면 코디네이터 코드를 제거하고 아래 코드로 대체해요.
// steps.accept(ToodaStep.loginIsCompleted)

return Observable<Mutation>.empty()
}

Expand Down