Immutable UI
Immutable UI is a collection of immutable data objects that mirror object-oriented user interface APIs. It's like a "shadow DOM" for .NET apps.
So far, only Xamarin.Forms has been bound, but I hope to also cover UIKit. And I still haven't bound events... wip :-)
Try it
-
Open ImmutableUI.sln in Visual Studio.
-
Select the project FormsButtonCounter in the Samples folder, and run it.
-
Click the "Increment" button a few times.
-
Note that the label increases.
Here's the UI code for that sample:
using Xamarin.Forms;
using ImmutableUI.Forms;
public class MainPage : ContentPage
{
public MainPage() => Content = BuildView().CreateView();
void SetState() => BuildView().Apply(Content);
int _counter;
Command _push => new Command(() => {
_counter++;
SetState();
});
ViewModel BuildView() =>
new StackLayoutModel(
children: new ViewModel[] {
new LabelModel(text: _counter.ToString(), fontSize: 42),
new ButtonModel(text: "Increment", command: _push),
},
padding: new Thickness(42));
}
Immutable UI Models
There is an immutable model class provided for every UI class in Xamarin.Forms.
To keep ourselves sane, each of these objects is suffixed with Model and is contained in the namespace ImmutableUI.Forms. (If the names weren't suffixed then terribly annoying name collisions would happen with the OOP API.)
For example, Xamarin.Forms.Button
has an immutable counterpart named ImmutableUI.Forms.ButtonModel
.
Thread Safety
UI Model objects can be used on multiple threads simultaneously since there's no risk of corrupting state. This means they can be shared between the UI thread and background workers without the need for synchronization.
Sharing
Immutable objects can be added to multiple UI trees to make multiple displays and caching easy.
Structural Equality
Every object implements deep versions of Equals
and GetHashCode
so
that you can reliably test if two UIs are identical. This also allows
you to use these objects as keys in dictionaries and sets.
Fluent Setter Interface
Let's be honest, immutable objects are a bit of a pain to deal with - sometimes you just want to set a property. Well, you still can't quite do that, but this library does provide a fluent interface to help out. Here it is in action:
var basicButton = new ButtonModel().WithFontSize(24).WithTextColor(Color.Green);
var okButton = basicButton.WithText("OK").WithCommand(ok);
var cancelButton = basicButton.WithText("Cancel").WithTextColor(Color.Red).WithCommand(cancel);
Note that the basicButton
above is being used like a template -
a fun little use of this fluent capability.
Complete Constructors
Every object's constructor accepts any of the properties stored in the object. In fact, constructing objects is your only chance to set these properties.
Every constructor parameter is a lowercased named after its corresponding property. Each one also has a default so you aren't forced to specify everything.
The fluent example above would can be written using constructors:
var basicButton = new ButtonModel(fontSize: 24, textColor: Color.Green);
Serializable
Every object is light-weight and can be serialized and deserialized using your favorite library.
This means your UI can now be serialized. This opens up many opportunities for quick state restoration, in-app GUI design, etc.
Works with the Mutable Objects
None of these objects would be useful unless they could work with their original mutable counterparts.
This is accomplished with two methods:
-
Create*
creates the mutable counterpart to the immutable object and sets its properties appropriately. For example,ButtonModel
objects have aCreateButton
method that returns aXamarin.Forms.Button
. This is the easiest way to create real UI objects from these lightweight immutable objects. -
Apply
sets the properties of the mutable oop object to match the properties of the immutable object. The following example shows its simplest use:
// Create the UI object independently of the immutable object
var button = new Button();
// Now create our immutable object model
var buttonModel = new ButtonModel(text: "Hello");
// Set the properties of `button` to those of the model
buttonModel.Apply(button);
Important note: While all methods on these model objects are
thread safe thanks to their immutability, you must run the Apply
method on the UI thread of your app because it
directly manipulates the UI. This synchronization is not handled
for you.
Related Work
- Elmish.XamarinForms brings the Elm UI programming model to Xamarin.Forms