Staty: View’s State Switching within MVVM Architecture
There are a lot of articles about implementing state-machine in iOS application, but they do not take into consideration one iOS specific thing — transition to a state may take some time to complete and while it’s completing transition to another state may not be possible.
Consider the following situation.
Your view is displaying some content (let’s call it state C). Now you want to show loading when you do some API call (it can be mere ActivityIndicatorView or it can be some controller which you created for this purpose) so you switch state to state Loading and your view is now reflects this state. Then consider that the API returned an error, which you want to represent as an native iOS alert. If your loading state is represented through a controller, then it must be completely dismissed before you will be able to show the alert, so you should consider adding some additional logic to handle this type of transitions.
In this article, I provide my solution for switching between states, that can be easily extended, modified and reused.
Let’s begin with State protocol. State must be able to represent itself on the target as well as dispose itself. In addition, we will provide any State with a delegate, I will explain it next.
As we talk about MVVM, it’s clear that a view model will have its state variable. Obviously, the view will observe it. To represent it with protocols let’s add following code:
Any view model that want to use states will confirm to StateDrivenViewModel.
Let’s see corresponding protocol for view:
Here I also provide UIViewController with bindWithState() method in order not to repeat this code in all view controllers. This method can be added also for any view you want (i.e. you can extend UITableViewCell).
For now, you can some basic structure: View observes its ViewModel’s state variable and calls enterOn method on it.
This method can be implemented in every state using provided target parameter. However, I recommend better approach: use Strategy pattern to provide State with corresponding Strategy using StateStrategyProvider that target (it’s often a View) will implement.
Let’s for example create LoadingState:
As you can see I also declare LoadingStateStrategy that must be able to show and hide loading and LoadingStateStrategyProvider that must provide an object that implements LoadingStateStrategy protocol. Finally, I provide default implemetation of the provider for UIViewController, that will return simple IndicatorView strategy. Let’s look at the strategy:
I intently add animation when hiding the indicator to show you how it will be handled.
Finally, the most important player: StateController. StateController will manage all transitions in a way you define.
As you can see, the controller also delegates to its observer when a state changes.
Moving on to the most interesting part: an implemetation of the controller that uses queue to make transitions consecutive:
When a state delegates its exit, controller dequeues it and invokes next state in the queue. If there is no next state, then exiting state won’t be dequeued in order to track current state. Queue class is just helper class, that can be found in repository via the link I provided at the end of the article. Note, that you can implement any custom logic: i.e. rejecting transition etc.
In this part I provide simple example of how to use this approach.
First, let’s create a view model that implements StatyViewModel (just a type alias for StateDrivenViewModel & CurrentStateObserver):
After its view will call setup method, state controller of BasicStateController class will be initialized and model will move to loading state, after 1 sec we will transit to three states in a row. You will see that all those transitions will be handled consecutively.
Here is a View which in this example is simple ViewController:
All we have to do is to accept StateAwareView protocol to be able to observe state, then implement provider protocols for corresponding states which we want to represent. Here we have nothing to implement because we have provided any UIViewController subclass with default implementation. Note that you can inject any strategy to a state just by providing it in corresponding method or create Factory which will do it etc.
- It’s reusable as you create one specific state strategy and use it anywhere you need it just by accepting a number of protocols
- You don’t have to worry about any other state logic and inconsistency when transitioning between states anymore: a state notifies when it exits and other state is being handled sequentaly
- It’s extendable: when you want to add different error showing logic (for example), just create corresponding strategy and provide it through provider protocol.
Just try it!
GitHub link: https://github.com/IsaAliev/StatyApp