Experiment
Put a clj-statechart somewhere in my mobile app! Learn from the process.
Observations
Certainly better for orchestrating interesting behaviour when compared with simple re-frame handlers. The registration process has lots of corner cases. More of those are covered. The code is by rights more maintainable. I was able to refactor as I expanded it with confidence. So lots of wins there.
Actions tend to be hardcoded to a specific use case. Primarily because they have to dispatch an event which the fsm expects.
By keeping the fsm/machine definition simple edn it's reasonably readable. By rights that should also make it easy enough to generate a diagram from.
The react navigation api is painful. By putting all related navigation in the machine I was able to avoid that some irritating corner cases (modals clobbering one another).
I was able to cater better for "api not responding" (timeouts) and "went offline during server interaction" cases more easily.
The delayed transactions require a scheduler which effectively becomes hidden state. Doesn't really fit with the re-frame model. There's no clean way to split it's "handler" behaviour from it's "fx" behavour without making a mess.
There are points of friction trying to get clj-statecharts to work with re-frame. I made a few changes for convenience (commit has some notes explaining why):
- This one saves wrapping every action in a wrapper. All my actions return modified state to trigger state change. That is a matter of taste.
- This one avoids things throwing when an event isn't expected. I think this one deserves consideration.
Wrapping the machine up as a reg-event-fx would have been easier if the fsm/machine could be told to look for state in a path.
I decided to wire the machine up as regular handlers so there wasn't an additional translation layer between the system events and the machine.
I pass re-frame coeffects to the machine as state. That should make it fairly flexible.
Feels similar but better organised than re-frame-async-flow-fx in this case. Not sure it's significantly different for bootstrapping. That's pretty linear.
Each of those cases could have a personalised error and behaviour, for now it's all the same message.
Open questions
- Is this approach is easy to debug and test? Actions could do less direct work and more dispatch to break things up but that seemed too much.
- Is this easy to test at the REPL?
- How likely is it the machine will get stuck in an unexpected state and lock the app?
- What it would take to do generative testing based on the machine definition? Seems like it should be an effective way to focus on possible combinations. Perhaps guards are declarative enough to be a mechanism for "predicate generators".