Navigation Controllers != State Machines

Brian Bernberg

GameChanger supports many different team types e.g. Little League teams, high school teams, travel teams and various others. Each of these team types require that we gather varying information from the user during the team creation flow. Consequently, there are various different paths a user can take during this flow. The app uses a navigation controller and various screens (with corresponding view controllers) to collect the relevant information. All of this information needs to be stored and finally, once a user has input all required information, the app makes an API call to create the team. Previously, we relied on these view controllers to each keep track of the team data. With a re-design we just implemented, the information is now stored in a central controller that also controls the team creation flow.

In our old design, team information was passed along from view controller to view controller via properties on each newly instantiated view controller. This required each view controller to not only know about downstream view controllers but also to be able to determine which view controller to push on the navigation controller – essentially, “next state” logic. Additionally, information collected early in the flow needed to be passed all the way down the chain to the final “Team Create” form. All of this was a clear violation of the Model-View-Controller paradigm. It was a brittle design that required a change in one view controller to propagate to all down stream view controllers…assuming whoever made the change realized this!

old code Our old approach in ‘Choose Team Type’ view controller. Note the next state logic and information passing via properties.

Our new approach is to use a central controller that maintains state, stores the collected information and determines which view controller gets pushed next onto the navigation controller. We use a state machine (an instance of YBStateChart) to organize the central controller, which makes the design concise and easy to read/understand.

state chart Create Team Flow state machine

With this new approach, each view controller is self contained and does not require knowledge of anything aside from the information it’s responsible for collecting. Once the user has input the necessary information on a screen, the view controller relays this information to the central controller, which in turn pushes the appropriate view controller on to the stack.

code2 New approach within ‘Choose Team Type’ view controller

code3 Example of event handling in the central controller for the ‘Team Type Chosen’ event

It is possible that a user needs to back up during the team create flow i.e. if they realize they made a mistake somewhere along the way. Fortunately, navigation controllers easily support this by popping view controllers from the navigation stack. Unfortunately, UINavigationController does not have a built-in notification for this situation. The central controller clearly needs to stay in sync with the navigation controller so we implemented custom code to send notification when the navigation controller is popping. This notification also allows the central controller to throw out any info that the popped view controller had collected since it’s no longer valid.

code4

Notification for indicating that a view controller is popping from the navigation stack Switching to a central, state-machine based, model-view-controller design has resulted in a cleaner design – more reusable, easier to understand and easier to modify as new team types come along. With self-contained view controllers, changes are localized to just that view controller & the central controller.