Comet is a .NET experiment inspired by Flutter and Swift UI for building cross-platform apps with C#.
Comet promotes a variation of the Model-View-Update pattern popularized by The Elm Architecture, Elmish, Fabulous and others. The major parts of MVU are:
- Model: the state of the app
- View: the state represented to the user
- Update: a way to update state via messages
State is (almost always) immutable and the changes flow in a single direction.
Defining State
Each View
has its own state and you can define this in a few ways. The most simple is to use State<T>
to wrap a field in a class that implements INotifyPropertyRead.
readonly State<int> count = 0;
Most views will probably have more complex state and you can represent that in the same way, however in order to get updates on property changes you'll want to extend BindingObject
and use the get/set helpers. You can see in the Comet project template:
public class CometRide : BindingObject
{
public int Rides
{
get => GetProperty<int>();
set => SetProperty(value);
}
public string CometTrain
{
get
{
return "☄️".Repeat(Rides);
}
}
}
Now within the View
you can declare your state just like that simple int
in the first example:
readonly State<CometRide> comet = new();
This is equivalent to some code you may see in the older Comet samples:
[State]
readonly CometRide comet = new();
Adding State in your View
All UI in Comet is a View
, and the [Body]
annotates the method that returns the view construction. This is where we can now binding our state to the UI. You "can" display the comet rides like this:
[Body]
View body()
=> new VStack {
new Text(comet.Rides.ToString())
};
Each View
takes a minimal set of constructor arguments, and here we pass in the state object and convert the int
to a string
. We can even use C# string interpolation to make this nicer.
new Text(()=> $"{comet.Rides} rides taken.")
Notice we are now using the function lambda ()=>
here. This is the optimal way to bind your state to views in Comet. Now it can reevaluate that property anytime the state of "comet" changes and apply just that property. The former method without the lambda requires the entire body of that view to be recreated.
Modifying State in your View
This is a noticeable place where Comet diverges from other MVU implementations. The message and update ceremony is streamlined. To increment the ride count in this example, let's bring in a Button
view which takes 2 constructor arguments: text for the title, and an action handler.
new Button("Increment", ()=>{
comet.Rides++;
})
When using
State<T>
without aBindingObject
you'll use.Value
to access the value you intend to change. Using the first example:()=> count.Value++
.
This change in state will trigger the body of the view to be reevaluated. Comet uses a virtual DOM, so this is extremely fast. A dif of the view is made, and the changes are applied.
Ride the Comet
There is more to be said about managing state beyond just the view, and other options such as Flux. For now this covers the basics and gets you going.
To get installed and a few other details, check out my previous setup blog.
And for a bonus, the author of Comet, James Clancey, joined the .NET MAUI Community Standup livestream last week to talk about Comet and answer questions. Enjoy!
Top comments (5)
Hi David!
I don't understand MVU pattern and how do CRUD operation.
I did it in my example in View.
Is that way right?
How can the code be separated into layers in this pattern?
Help me please.
Thanks.
Wow thanks for sharing this! Is this all in C#?
Yes, all C#.
Because it's based on .NET MAUI you could mix in some other UI tech, but the idea is to keep it simple and use the power of C# to the fullest.
Cool overview. How does flux participate here? Is it an optional extension over Comet or some sort of replacement?
Check out github.com/SuavePirate/Comet.Flux