RxSwift

[RxSwift Community] RxFlow 사용하기 1

Developer.Paul() 2021. 6. 17. 18:30

이전 글을 읽어보시지 않았다면, 대강 느낌을 위해서라도 한번 읽어보시길 추천드립니다:)

https://developerpaul.tistory.com/24

 

[RxSwift Community] RxFlow - 시작하기 전,

이번 포스팅에서는 RxCommunity 중에서 가장 인기가 많은 RxFlow에 대해서 알아보려고 합니다. Coordinator Pattern에 대한 글을 적다가 포기했는데, 이게 쉽게 쓰면 엄청 쉽고 복잡하게 쓰면 정말 복잡하

developerpaul.tistory.com

예제는 아래 링크에 있습니다:)

https://github.com/Developer-Paul-t/RxSwift

 

Developer-Paul-t/RxSwift

Contribute to Developer-Paul-t/RxSwift development by creating an account on GitHub.

github.com

RxFlow의 첫 시작은 FlowCoordinator라는 총사령관을 만드는 것부터 시작합니다. 그리고 이 FlowCoordinator는 태초의 공간을 만들고 최초의 상태를 받아서 첫 navigation 실행을 합니다. 즉, 첫 화면을 띄우는 것이죠. FlowCoordinator는 RxFlow에서 이미 제공하고 있기 때문에 다음과 같이 인스턴스를 생성해주면 됩니다. 전 SceneDelegate에서 작업했습니다:)

 

 

FlowCoordinator의 핵심은 coordinate라는 메소드입니다. 이 메소드의 파라미터로 navigation의 root가 되는 공간(Flow)과 상태(Step)를 방출하는 Stepper를 받아서 첫 navigation을 실행하기 때문입니다. 아무튼 이 첫 실행을 위해서 상태(Step), 공간(Flow), 상태를 방출하는 녀석(Stepper)를 차례대로 만들어봅시다.

 

 

1. 상태(Step) 만들기

 

"어느 화면으로 가라!"가 아니라 '상태'라고 표현하는 이유는 RxFlow를 비롯한 함수형 프로그래밍은 선언형 프로그래밍을 지향하기 때문입니다. 잘 아시다시피, 어떻게 해라가 아니라 무엇을 할까에 집중되어 있죠. 여기서 case를 추가하실 때도, 어떤 상태인지를 묘사하는 것이 좋습니다. 추가로 Step에 관해서 RxFlow Github에 다음과 같은 조언이 있습니다. 

 

The idea is to keep the Steps navigation independent as much as possible. For instance, calling a Step showMovieDetail(withId: Int) might be a bad idea since it tightly couples the fact of selecting a movie with the consequence of showing the movie detail screen. It is not up to the emitter of the Step to decide where to navigate, this decision belongs to the Flow.

 

이 조언의 핵심은 '독립적'으로 구현하라는 것입니다. 독립적이라는 말은 Stepper와 Flow의 역할을 명확하게 구분해야 한다는 뜻입니다. 위의 예시를 실제로 구현하게 되면, Stepper에서 showMovieDetail(withId: Int)라는 case를 방출하도록 할겁니다. 하지만 파라미터에 Int를 넣어줘야 하죠. 이렇게 되면 Stepper에서 단순히 Detail에 가는걸 정하는 것 뿐 아니라 어느 Detail까지 가는지도 정해야 합니다. 그렇게 되면 사실 더 많은 정보와 코드가 Stepper에서 있어야 하죠.

 

하지만 이건 Stepper의 역할이 아닙니다. Stepper는 case를 그냥 방출하는 역할입니다. 어디로 가서 어떤 걸 보여줄지는 Flow에서 정해야합니다. 그래도 쓸 수밖에 없는 상황이 있습니다. 그 때는 그냥 ... 잘 쓰시길... 아무튼 필요한 상태를 고민해보고 case를 적습니다. 좋은 점은 나중에 추가해도 된다는 것!

 

2.  방출자?(Stepper) 만들기

Stepper는 이렇게 새로운 상태를 방출하는 역할을 가져야 하는 친구들에게 Stepper 프로토콜을 준수하게 하면 됩니다. ViewController 혹은 ViewModel이 Stepper를 준수해서 사용하면 좋죠. 저는 나중에 ViewModel에 구현할 예정입니다. Stepper 프로토콜에서는 steps라는 PublishRelay가 가장 중요합니다. 이곳에서 step을 받기도, Flow로 넘기기도 합니다. 내부에서 step을 방출하는 메소드를 구현할 수도 있는데 이건 readyToEmitSteps를 통해서 할 수 있습니다. initialSteps는 listened되었을 때 처음으로 방출하는 step이므로 이 Stepper(AppStepper)를 사용했을 때는 바로 homeIsRequired라는 step이 처음 방출될 겁니다. 이후에 이 Stepper에서 방출하는 step이 없기 때문에 최초의 공간을 만들 때만 사용될 예정이고, 다른 Flow에서는 다른 Stepper를 사용할 예정입니다.

 

3. 공간(Flow) 만들기

 

우리가 만든 상태들이 작동할 공간을 만들어봅시다. 이 공간만 만들어지면 navigate가 작동해서 일련의 결과를 내뱉을 겁니다. 먼저, AppFlow 클래스를 만들어 Flow 프로토콜을 채택합니다. Flow에는 navigation의 base가 될 root라는 Presentable 프로퍼티, adapt라는 기존 step을 새로운 step으로 교체해주는 메소드, 그리고 핵심이 될 navigate라는 실행 메소드를 준수해야 합니다. adapt는 구현할 필요가 없어서 제외(extension에 기본 구현이 되어있음)하고, root를 구현해줍시다. 보시는 것처럼 앱에서 처음으로 실행되기 때문에 window를 root로 설정해줍니다. 그리고 navigate에는 들어오는 step에 따라 다른 함수가 구현되도록 합니다.

 

 

step에 따른 실행결과는 navigateToHomeFlow 메소드 하나만 구현했습니다. 이 메소드는 다음 Flow를 구현하고 있습니다. "으잉? 그냥 거기서 띄워주면 되지 왜 굳이 다음 Flow로 넘어가?"라고 질문하실 수 있으실 것 같습니다. 일단 연습삼아 다른 Flow로 넘어가는 것도 있고, 현재 Flow(AppFlow)의 root에서는 navigationController를 embed하지 않았기 때문에 navigationController가 root인 Flow로 넘어가려는 것도 있습니다. 또 위에서 살펴봤던 것처럼 현재 coordinator에 장착된 Stepper는 다른 step을 방출하는 구현이 되어있지 않습니다. 그래서 새로운 Stepper를 채택하고 싶은 것도 있었습니다. 아무튼 이런저런 다양한 이유로 새로운 Flow(MainFlow)와 Stepper(ArticleViewModel)를 구현하고 위와 같이 인스턴스를 생성하겠습니다.

 

새로운 Flow로 넘어가려면, 객체를 생성하는 것 말고도 Flows.use()라는 타입 메소드로 root를 교체해주는 것이 필요합니다. 이렇게 모든 준비가 끝나면, FlowContributors를 리턴할 준비가 되었습니다. (로직이 끝도 없죠? 이래서 어렵습니다.) FlowContributors는 enum으로 5개의 case를 가지고 있습니다.
- none은 Flow가 추가적인 navigation을 하지 않는 것,

- one은 Flow가 하나의 Step에 하나의 FlowContributor( s가 붙어있는 것과 다릅니다.) 트리거하는 것,
- multiple은 Flow가 하나의 스텝에 여러개의 FlowContributor를 트리거 하는 것,
- end는 Flow가 자신을 해지할 FlowContributor를 트리거 하는 것,

- triggerParetnFlow는 .one(flowContributor: .forwardToParentFlow(withStep: Step))와 동일합니다.(deprecated)

 

이렇게 5개의 선택지 중에 적절한 case를 고르고 FlowContributor를 트리거 하게 됩니다. 그렇다면 FlowContributor를 트리거한다는건 무슨 뜻일까요? 먼저 FlowContributor는 말 그대로 Flow의 활동에 기여한다는 뜻입니다. 이 말은 FlowContributor가 contribute 하게 되면 Flow가 활동(navigate가 작동)하게 된다는 의미입니다. 다시 말해서, stepper에서 step이 방출되었고 Flow에서 이를 받아서 navigate 함수를 호출한다는 뜻이기도 합니다. 그렇기 때문에 FlowContributor에는 다음 navigation에 활동할 Flow와 Stepper를 파라미터로 넣어줘야 합니다. 우리는 이미 AppFlow가 아닌 MainFlow에서, AppStepper가 아닌 ArticleViewModel에서 하기로 했었고 인스턴스를 생성했었죠. 그래서 위와 같이 .contribute의 파라미터에 새로운 Flow와 Stepper를 넣어준겁니다.

 

FlowContributor도 enum 입니다. 직접 지정해줄 경우에는 .contribute를 사용하고, 현재 Flow에서 실행하고 싶을 경우에는 forwardToCurrentFlow에 Step만 지정, 부모 Flow에서 실행하고 싶을 때는 forwardtoParentFlow에 Step만 지정하는 방식으로 구현됩니다. 이렇게 구현하면,  return 되는 FlowContributors에 의해 바로 다음 navigation이 action됩니다.

 

이번 글은 일단 여기까지입니다! 아직 첫 뷰를 띄우지도 않았죠... 아무튼 글이 길어지니 여기서 마무리하고 다음 글에서 MainFlow와 ViewModel에서 Stepper를 채택한 것 등에 대해서 구체적으로 이야기 나눠보겠습니다:)