Design Patterns: Locks and Keys

January 26, 2019

John Sundell’s Locks and Keys design pattern (also covered in this talk) is worth a look. In short, rather than having a manager class with, say, var currentUser: User?, you create a specialized factory class that represents the state of having a user (var currentUser: User), which is in turn used to create the appropriate view controllers with the user populated/injected.

For the past few years I’ve considered the Coordinator pattern (popularized on iOS by Soroush Khanlou) to be my go-to high level app architectural pattern. For me, Locks and Keys closely resemble how the Coordinator pattern has worked out in practice, particularly after several years of Swift. As an example, a root AppCoordinator might create and defer to LoggedInCoordinator and LoggedOutCoordinator, depending on the state of the app. These coordinators then have private methods to create populated view controllers.

Recently I spent some time experimenting with Flutter, writing an app that shows a “Connect to Server” screen prompting for address and port when not connected, and the rest of the user interface when it is connected. Flutter seems to encourage a similar pattern to the above: if the app is not connected to the server, build() returns a widget for the “Connect to Server” screen. If it is connected, a different widget is built, with the reference to the connection passed to its constructor.

A few observations of differences and similarities between Swift/UIKit and Dart/Flutter here:

  • Swift’s optionals must be dealt with. Without the above pattern, as Sundell points out, you end up with a lot of guard-assertionFailure clutter – error handling code you never expect to be executed.
  • In Dart, any reference can be null, but there’s also no need to unwrap them. As a result I imagine most code only rarely checks for null, resulting in the occasional “Oh, I forgot to pass that in…” crash.
  • If you use UIKit storyboards to load your view controllers, you can’t override the initializer and must instead rely on a convention of populating the injected dependencies, models, etc. One could argue that this is roughly equivalent to pitfalls of Dart’s “everything is optional”. (This issue with UIKit view controller instantiation is well understood by the community.)
  • Flutter’s design encourages passing dependencies down – stark comparison to UIKit storyboards.

Check out John Sundell and Soroush Khanlou’s posts if you haven’t already.

Tags: ,