In the Coordinator Pattern: No, Router: Yes article I explained why and how I prefer Router for iOS app navigation rather than coordinator. Now I want to rename Router to Flow. Because the definitions and responsibilities that I assign to Flow differs from the original Router concept that was introduced in VIPER. Flow has still some similarities to the Router.
Before going deeper, let’s define two terms to be clear. Suppose we have two modules, the source and the target. Each module contains its own view controller, use case, presenter and, anything else it needs to work properly. We need to navigate from the source view to the target view and vice versa. We also want to send data between these modules back and forth.
The elements in each module have their own responsibilities:
View Controller is responsible for updating UI and transmitting user interactions to other elements.
Use Case is the place that business logic goes there. It receives user interactions and/or other events, processes them an, respond to them.
Presenter receives the processing results from use case, convert it into a representable format and delivers them to the view controller.
What if the user initiate a request that must be done in a different view than the current view of app. It is obvious that we need to instantiate and navigate to the target view controller and pass data back and forth to execute the request. The important question is that which of the above modules must create the target module, run the navigation code and manage data transmission? The answer is clear, none of them. As we want to follow the single responsibility of SOLID, navigation and inter-module communication operations should not be defined in their responsibilities. Hence we need to a new role and I decided to call it Flow.
Flow Responsibilities
- Acts as a gateway to show and dismiss a target view controller, and
- pass messages back and force between the source and target view controllers
Creation of Target View Controller
Flow uses a Factory to instantiate the target module. The Factory makes the target view controller, presenter, use case and any other components that a ready-to-use module needs to work.
Showing and Dismissing of Target View Controller, and Inter-module Communication
Source Flow is a service for the source use case similar to any other services, such as persistency or network services, but with an important difference. Flow needs to make a change in the current view of the application to achieve its goals. in other words, main use case sends the requests to the main flow that must make a change in the current view of the application.
How does Flow Work?
- Flow uses the factory of the the target module. The factory of the target module should retrieve two objects:
- Target view controller.
- Target use case as a service. (I explained what I mean by “as a service” in number 5)
- When the target use case finishes its task, sends the result back to the main flow. Main flow and main use case should decide about dismissing the target view controller or not. For example they may decide to repeat the task or initiate another tasks on the target module, so there might be no need to dismiss the target view controller currently. It helps us to have more single responsible view controllers because they do not decide about dismissing themselves.
- The main flow communicates with the target use case instead of target view controller. It helps us to keep business logic in use cases and having more single responsible view controllers.
- Main flow first call a method on the target use case, for instance to pass some data to it, then shows the target view controller. Target view controller can ask for this data from target use case on view will appear event.
Alternatively, this process can be done by passing the data as initialize parameters to the factory of the target. - Target use case implements two different interfaces as input boundary:
- Use Case Interactor Protocol, that the target view controller uses. The Interactor implies the fact that user uses these APIs in his/her interactions.
- Use Case Service Protocol, which is imposed to the Flows. The Service points to the fact that these APIs are used by a client internally which is Flow here.
That’s enough for theory. Following screenshots are form an example iOS app that show how to use Flow. It’s a simple app that you can trace the codes simply to get the main idea of Flow.
Form left to right, you see a list of students on the main view. When you select a student, you will go to the “Edit Student Name” view, where you can change student name. There is an “Edit Age” button that moves you to the “Edit Student Age” view. There are two buttons on the “Edit Student Age”, “Done” that moves back to the “Edit Student Name” and “Back to Student List” button that returns back to the main view of the app and all changes are applied to both name and age of the selected student.
The following diagram shows the app architecture. You can find the source of this diagram in the project source code, in documents folder. The names are self-descriptive.
There is also a SceneFlow that configs and shows the main view controller of the app that differs a little from the other Flows and keeps a reference to the app window. Other inter-module communications follow the principals of the Flow that I mentioned above.
Download Flow app from GitHub.
Summary
Flow helps you to isolate your navigation and inter-module communication from all other parts of your app that improves modularity, readability, testability, extendibility and reusability of your codes.