Decoupling inlined UI code
When writing code to drive a user interface, there’s an unfortunate default tendency to scatter logic everywhere. At least, that’s what I do unless I apply conscious effort to avoid it. In this short post, I explain the problem and a simple solution.
Bad: Inline Logic
Suppose you need to disable a feature F when activities X or Y or Z are happening. The default way to solve this problem (for me when not thinking ahead) is to just find the code starting/stopping X/Y/Z and insert the necessary logic. Like this:
void OnClickedStartX() {
...
FButton.Enabled = false;
...
}
void OnFinishX() {
...
FButton.Enabled = true;
...
}
void OnStartY() {
...
FButton.Enabled = false;
SomeOtherButton.Enabled = false;
...
}
...
Don’t do this. The end result is a bad place to be. Now, in order to determine if F is implemented correctly, unrelated architectural details like “Is OnClickedStartX the only way to start X?” and “Can Y be happening at the same time as Z?” must be understood. To make matters worse, those details are unlikely to be explicitly stated anywhere. In other words: the code is too highly coupled, making it brittle and unapproachable.
The “technique” exemplified above spreads single features across multiple methods and, conversely, spreads the code in single methods across multiple features. Verification and understanding can’t be done in small chunks, because everything ends up interconnected. Determining if a feature is implemented correctly requires determining the correctness of 10 methods, each of which involves code for 10 features, each of which … You get the idea. You will lose a lot of time if you take this approach.
Good: Triggered Logic
I decouple this sort of “F when X/Y/Z” code with events (or some other form of triggered callback). Instead of putting code-about-F inside code-about-X, you have an ‘OnXChanged’ event that code-about-X raises and code-about-F consumes. In this case, because triggered values are being combined and transformed, I think the clearest implementation uses reactive extensions (a.k.a. linq-to-events). The resulting code looks like this:
// the running states are exposed in a way that can be observed
ObservableValue<bool> isXRunning = new ObservableValue<bool>(false);
ObservableValue<bool> isYRunning = new ObservableValue<bool>(false);
ObservableValue<bool> isZRunning = new ObservableValue<bool>(false);
// the start/stop methods manage the relevant exposed states
void OnClickedStartX() {
...
isXRunning.SetValue(true);
...
}
void OnFinishX() {
...
isXRunning.SetValue(false);
...
}
...
// F's logic is hooked up indirectly, via the observables
void Setup() {
...
// create an observable that is false when any of X, Y or Z are running
IObservable<bool> shouldFBeEnabled =
Observable.CombineLatest(
isXRunning,
isYRunning,
isZRunning,
(x, y, z) => !(x || y || z))
.DistinctUntilChanged();
// drive the button's enabled state with the observable's value
shouldFBeEnabled.Subscribe(isEnabled => FButton.Enabled = isEnabled);
...
}
This code is a bit longer, but much easier to understand and verify in pieces. Everything having to do with F is in one place. Everything having to do with X is in another place. Thus the code-about-F can be understood and verified independently of the code-about-X.
By using Rx, instead of raw events, we also get to take advantage of methods like CombineLatest and DistinctUntilChanged (why didn’t they call it WhenDifferent?). This makes life a lot easier, which is not too surprising since this sort of situation is exactly what Rx is designed for.
Summary
Decouple UI code with events and Rx. Not really an earth shattering idea (people have said it before). But, the benefit is significant enough that it bears repeating.
Next week: What if we don’t know what disables F? How can independent components, like plugins, affect F without trampling on each other’s toes?
—
—
Discuss on Reddit
—
Twisted Oak Studios offers consulting and development on high-tech interactive projects. Check out our portfolio, or Give us a shout if you have anything you think some really rad engineers should help you with.
Older Posts
- Eventual Exceptions vs Programming in a Minimal Functional Style
- The Mystery of Flunf
- Explain it like I’m Five: The Socialist Millionaire Problem and Secure Multi-Party Computation
- Computer Science Blows My Mind
- A visit to Execution Labs in Montréal
- Transmuting Dice, Conserving Entropy
- Rule of Thumb: Ask for the Clock
- Rule of Thumb: Use Purposefully Weakened Methods
- Rule of thumb: Preconditions Should be Checked Explicitly
- Intersecting Linked Lists Faster
- Mouse Path Smoothing for Jack Lumber
- My Bug, My Bad #2: Sunk by Float
- Repeat Yourself Differently
- Grover’s Quantum Search Algorithm
- Followup to Non-Nullable Types vs C#
- Optimizing Just in Time with Expression Trees
- When One-Way Latency Doesn’t Matter
- Determining exactly if/when/where a moving line intersected a moving point
- Emulating Actors in C# with Async/Await
- Making an immutable queue with guaranteed constant time operations
- Improving Checked Exceptions
- Perishable Collections: The Benefits of Removal-by-Lifetime
- Decoupling shared control
- Linq to Collections: Beyond IEnumerable<T>
- Publish your .Net library as a NuGet package
- When null is not enough: an option type for C#
- Unfathomable Bugs #5: Readonly or not
- Minkowski sums: examples
- My Bug, My Bad #1: Fractal Spheres
- Working around the brittle UI Virtualization in Windows 8
- Encapsulating Angles
- Unfathomable Bugs #4: Keys that aren’t
- How would I even use a monad (in C#)?
- Useful/Interesting Methods #1: Observable.WhenEach
- Unfathomable Bugs #3: Stringing you along
- Anonymous Implementation Classes – A Design Pattern for C#
- Tasks for ActionScript 3 – Improving on Event-Driven Programming
- Minkowski sums and differences
- Non-Nullable Types vs C#: Fixing the Billion Dollar Mistake
- Unfathomable Bugs #2: Slashing Out
- Script templates and base classes
- Unity font extraction
- Abusing “Phantom Types” to Encode List Lengths Into Their Type
- Constructive Criticism of the Reactive Extensions API
- Quaternions part 3
- Quaternions part 2
- Quaternions part 1
- Unfathomable Bugs #1: You can have things! You can have things IN things! You can have …
- Coroutines – More than you want to know
- Asset Bundle Helper
- The Visual Studio goes away
- .Net’s time traveling StopWatch
- Polish
- Introducing Catalyst
