ImmutableUI and TDD with Xamarin.Forms

MVVM and XAML has failed me. A promise of abstracting to make reusable components, but they never get reused. Layers of complexity for virtually no benefit. If reuse is to happen as planned, it would require almost mystical level foresight into the future, across multiple projects. In the real world, our apps are self contained, and we don’t reuse.

We then code our views in XAML, but what has this given us? A way to neatly separate UI from code? But has it really? When you look at the converters, behaviors and more we need to keep coding, we keep building a bridge between C# and XAML. I have to write a converter, to do what is equivalent of ‘ != ‘ in C#. XAML is verbose and getting out of hand. Then I have to keep wiring up another property to send data back and forth between XAML and the ViewModel.

XAML was also meant to be done by designers, then passed to developers to wire up. I have never in my entire professional career ever seen this happen, and for many reasons, can never see it happening.

ImmutableUI

Spending some time on Flutter, I realized the way they built their UI was incredible. Their UI was an immutable model. You had to rebuild your UI, every time you wanted up to update a property, then send that to the Flutter engine to redraw. In Flutter this works really well, because there is no native components to manage. In Xamarin.Forms, this can’t be done, with the base framework. Frank Krueger built an extension (not production ready just yet) called ImmutableUI, that allows you to work this way. As a basic example, this is how a page will work.

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));
}

ImmutableUI has a shadow model, where everything has the name Model at the end, e.g. LabelModel, ButtonModel. This is taken, and a differential is done on the actual native components. This means Xamarin.Forms isn’t actually wiping its UI and being rebuilt each time. The UI is built in C#, no XAML here.

Keeping your UI Immutable and in C# has a number of benefits

  1. To change something you have to rebuild it, hence you are aware of the entire UI state every time.
  2. Your UI state is easily accessible in a number of relevant local variables
  3. Since you are in C# you can just write any conversion or presentation code as desired.
  4. C# is a powerful language, you only have to stay in 1 language, and not cross between XAML and C#.

Context

Please read before proceeding.

  1. MVVM makes some more sense in traditional Xamarin, where you actually share the VM’s and have different Views. But I am exclusively talking about Xamarin.Forms in this post.
  2. Yes you can write a good app with MVVM and XAML. My point here is the abstractions are overly tedious and have little value, in most mobile app development scenarios. But that doesn’t mean the end result of an app written in MVVM and XAML is bad.
  3. I won’t be using ImmutableUI in the examples below because:
    1. It’s not production ready, as it doesn’t have events wired up
    2. I wanted to keep things simple. But please note, this is not how you would build the UI in a production app. You would have to use ImmutableUI.
  4. We are using C# here. F# might be able to push further with more immutable functions, but I’m keeping this in the more widely used programming language. Because C# is a an OO language, state and OO concepts are present in these examples.
  5. You can use many different Test frameworks, ReactiveX, IoC containers and so forth. I’m just trying to show the basics. Build upon it how you like, with your favorite frameworks and tools.
  6. I haven’t done this in any real world project. I’m not saying this will solve the worlds development problems, nor that it is completely free of problems.

The idea of this approach is that it leads to a simpler application, and removes patterns that weren’t actually giving us any benefit.

Source Code (GitHub Repo): Pattern

Workflow

First, create an IWorkflow interface and apply to any class. I just tacked it on to the Application class for simplicity. I actually don’t see anything wrong with this approach, but you can pull it into it’s own class if you really want to.

This handles your entire apps navigation.

public interface IWorkflow
{
	Task Navigate(Page page, string state, object parameter);
}

public class App : Application, IWorkflow
{
	public App()
	{
            // You can change this later to whatever your starting page is.
            MainPage = new ContentPage();
	}

	public async Task Navigate(Page page, string state, object parameter)
	{
	}
}

Template Page

The template page is what all new Views will be based off. We are setting this up, so we can easily get into Test Driven Development next. This will closely match the ImmutableUI layout shown above.

public abstract class TemplatePage: ContentPage
{
	protected readonly IWorkflow _navigation;
	public TemplatePage(IWorkflow navigation)
	{
		_navigation = navigation;
		Build();
	}

	protected virtual void Build()
	{
		Content = BuildView();
	}

	protected abstract View BuildView();
}

Testing

It’s testing time and we haven’t even started to write our application yet. Before you get started, we do need some helper code. MockPlatformService.cs and VisualTreeHelper.cs. These just help initialize Xamarin.Forms, and help find elements in your UI. I am using xUnit and Moq for testing, but you can use any testing framework you want.

LoginPage

Picture a simple login page, with 2 entry elements and a button. We know that we need to validate the entry fields, press the button, then navigate to another page. In the main project create an empty LoginPage.

public class LoginPage : TemplatePage
{
	public LoginPage(IWorkflow navigation) : base(navigation) { }
}

In the test project, lets create a new LoginPageTests. This will initialize Xamarin.Forms, create a new IWorkflow mock.

public class LoginPageTests
{
	readonly Mock _workflowMock;
	readonly LoginPage _loginPage;
	public LoginPageTests()
	{
		XamarinFormsMock.Init();
		_workflowMock = new Mock();
		_loginPage = new LoginPage(_workflowMock.Object);
	}
}

What elements will we have on our page? Lets just write them down here. These are just the names of the elements you will have on your page.

readonly string UsernameEntry = "UsernameEntry";
readonly string PasswordEntry = "PasswordEntry";
readonly string LoginButton = "LoginButton";

Now lets create a test. I will just make a simple test to say that the

[Theory]
[InlineData("12345678", "Password", true)]
[InlineData("123456", "Password", false)]
public void Validate_Username(string username, string password, bool isValid)

Inside this test, we confirm the initial state of the UI, make changes, then confirm the button is now enabled.

var view = _loginPage.Content;

var usernameEntry = view.GetElementByName<Entry>(UsernameEntry);
var passwordEntry = view.GetElementByName<Entry>(PasswordEntry);
var buttonEntry = view.GetElementByName<Button>(LoginButton);

// Initial Checks
Assert.False(buttonEntry.IsEnabled);
Assert.True(passwordEntry.IsPassword);
Assert.Null(usernameEntry.Text);
Assert.Null(passwordEntry.Text);

// Act
usernameEntry.Text = username;
passwordEntry.Text = password;

// Get new UI (this is only applicable because I'm not using ImmutableUI)
// Otherwise these 2 lines of code wouldn't be needed
view = _loginPage.Content;
buttonEntry = view.GetElementByName<Button>(LoginButton);

// Assert
Assert.Equal(isValid, buttonEntry.IsEnabled);

This is testing that the button will be enabled when the 2 entry elements meet requirements. This test will fail when run, mainly because the elements aren’t found, but we wrote the tests before the UI. When the button is clicked, we will also want to test that it raised a navigation request.

I flipped navigation on its head. The View doesn’t care about navigation, it has now outsourced that responsibility to the IWorkflow service. Here the LoginPage is expected to say Authenticated, when it is ready to move to the next page. It doesn’t know where it is going, just that its state has changed.

[Fact]
public void Validate_Navigation()
{
	var view = _loginPage.Content;

	// Valid Page
	var usernameEntry = view.GetElementByName(UsernameEntry);
	var passwordEntry = view.GetElementByName(PasswordEntry);
	usernameEntry.Text = "12345678";
	passwordEntry.Text = "12345678";

	var buttonEntry = view.GetElementByName<Button>(LoginButton);
	buttonEntry.Command.Execute(null);

	_workflowMock.Verify(x => x.Navigate(_loginPage, "Authenticated", null));
}

Navigation

Let’s test what should happen when that navigation request is sent. I will create a new AppTests.cs in my test project, initialize and test that the page is moved. Since this is testing that the LoginPage will go to the MainPage, you should create an empty MainPage.cs in your main project first.

public class AppTests
{
	readonly App _app;
	readonly LoginPage _loginPage;

	public AppTests()
	{
		XamarinFormsMock.Init();
		_app = new App();
		_loginPage = new LoginPage(_app);
	}

	[Fact]
	public async void LoginPage_Navigation()
	{
		await _app.Navigate(_loginPage, "Authenticated", null);
		Assert.IsType<MainPage>(_app.MainPage);
	}
}

This test, calls the page type and state, as we just tested the LoginPage does. Now we test the MainPage is of the correct type. This is a simple example, and your real world tests would expand to checking how many pages in the NavigationPage, and so forth. Although you can see how simple that would be to do. Your app navigation is just one small piece of testable logic.

Building the UI

Now that the tests are built, lets build the UI. I added a small extension called TextUpdated, that processes a value when the Text changes. This will not be required once ImmutableUI, has events wired up.

We first build our View. Remember I have shown this using Xamarin.Forms elements, not ImmutableUI, to keep the examples relatable and because ImmutableUI isn’t completed yet. You would never use this in a real world production app.

protected override View BuildView()
{
	return new StackLayout()
	{
		Children = {
			new Entry() { AutomationId="UsernameEntry", Text=_username }.TextUpdated(UpdateEntry),
			new Entry() { AutomationId="PasswordEntry", IsPassword = true, Text=_password }.TextUpdated(PasswordEntry),
			new Button() { Text = "Login", Command=LoginCommand, AutomationId="LoginButton", IsEnabled = _isValid }
		}
	};
}

You will see that building a UI in C# isn’t hard or unreadable. You must use AutomationId to name each element. I have used my workaround, TextUpdated to process TextChanged events. Each time the text changes the UI will be rebuilt.

Note: Attaching to these events is perfect for ReactiveX, and makes more sense in this setup. Rather than hooking into the binding process of a View and ViewModel.

void UpdateEntry(string value)
{
	_username = value;
	Build();
}
void PasswordEntry(string value)
{
	_password = value;
	Build();
}

// State
string _username = null;
string _password = null;
bool _isValid = false;

An immutable UI, means that you update the variables locally, then rebuild the UI again based on the new values.

We can add some validation logic in the build process, to ensure that our Unit Tests pass.

protected override void Build()
{
	Validate();
	base.Build();
}

void Validate()
{
	_isValid = _username?.Length >= 8
		   && _password?.Length >= 8;
}

And finally, we want to wire up the LoginCommand. To keep it simple, I haven’t connected it to the API, but it is simple to do.

Command LoginCommand => new Command(async () =>
{
	Build();

	if (_isValid)
		await _navigation.Navigate(this, "Authenticated", null);
});

I realize this contains a magic string. This is just to ensure the example is simple. You can store these values in constants or enums, or any method you prefer.

Run your Unit tests now, and the LoginPageTests.cs will succeed. Always update the tests with requirements first, then build out your View.

Building the Navigation

This is a simplistic view of a workflow controller. All you need to do, is see which page made the navigation request, see which state they set themselves in, and push the new page. In App.cs in your main project, expand the navigation function as appropriate.

public async Task Navigate(Page page, string state, object parameter)
{
	if (page is LoginPage && state == "Authenticated")
		MainPage = new MainPage(this);
}

Composable Services

What about the rest of the app. Answer is, make everything else a service.

Make an IAPIService, IMyAppService, or ICameraService. Hardware, business logic or repository, there is no reason why a View can’t have a service injected into it directly. ViewModels and Models were just layers that didn’t add much other than more layers, for non-reusable abstraction. Need a place to store state to share across views? Make another service. How big or small you make them, is up to your application requirements. If you were using F# you could do away with many interfaces and have composable functions.

Summary

That’s its. We have Views, we have Services, and we stick them all together as needed. Unit tests are easy to write first, and can even remove the needed for UITest in some scenarios.

I have cut away at many levels of abstraction. You may look at this and think if the interface changes, there will be many places you will have to change. But changes at such a big level in the project, rarely occur, and if they do, you have bigger problems than some interface definition changes. It normally requires entire sections of code to change with it regardless.

This is just what I have come up with and learnt after almost 4 years of Xamarin.Forms development. It may work for you, I do realize it might not work for some situations, but for me, it would have worked better for every single app I came in contact with.

At the end of this post I realized, this switching approach, to a View-First approach. MVVM keeps you thinking about how to display data to your user. It should be the other way around. Your user doesn’t care about your backend, you develop the Views your user wants, then you work out what needs to happen when interacting to services.


XAMARIN WEEKLY NEWSLETTER

Subscribe to a hand-picked round up of the best Xamarin development links every week. Published every Friday. Free.

Signup to Weekly Xamarin

* I do not maintain or run this newsletter, but I do recommend it.

Microsoft MVP | Xamarin MVP | Xamarin Forms Developer | Build Flutter

Related Posts

13 Comments

  1. Julian Monono

    Hi Adam,

    I really enjoyed this article! Having just completed the first version of a side project using Xamarin Forms, I can relate to the headaches that MVVM can cause. I wonder how this approach would scale when dealing with ListViews, ViewCells, DataTemplates etc. – but I’m keen to give it a try!

    Thanks!

    1. Adam Pedley

      Thanks πŸ™‚

      This approach doesn’t actually redraw the ListView, it just updates it if changed, hence it would act the exact same way as a regular XF app would. Performance will be roughly the same.

  2. nhwilly

    I am literally just about to start building the UI for my app.

    It has always seemed to me that XAML was lots of work and kind of confusing, but I really didn’t there was another way.

    It’s probably too “early days” for this to be useful for this app, and I’ll have to continue with MVVM/XAML.

    Maybe next time.

    Looking forward to hearing about this progress.

  3. Wojciech Gebczyk

    Recently when I’ve been trying to keep under control logic that mangled with app state in different parts and coordinating that, I’ve came up with similar approach – React-Redux style.

    Whole app state is changed/mutated by calling commands that was dispatched to redactor functions, that modified the state. The store published new observable state, on which UI reacted and updated UI.

    That’s tempting approach, but with it, we are discarding benefits of XAML (separation of UI declaration, from pure UI logic AND auto object instantiation AND etc).

    using “new” instead of XAML, using “=” instead of Binding, recreating visual and logical tree on state changes instead relaying on WPF optimizations, etc MIGHT make sense, but at what cost?

    Could you describe what problems you are trying to solve with this approach?

    But definitely something new, worth to consider and think on outcomes…

    1. Adam Pedley

      In this particular code approach, it allows you to create the state exactly how you want it without bindings, converters and other tools having to make changes afterwards, hence reducing complexity or unintended side effects.

      Many people keep telling me they like the separation of UI and Code, and I certainly agree that presentation logic / business logic should be kept separate, but how is having the UI in a markup language benefiting us?

      We have to use converters, bindings, behaviors etc to go back to code to create certain presentation logic. Its all UI related, but now split between code and XAML. And it makes no sense.

      This approach takes the Bindings, converters, behaviors, XAML, etc into just BuildView(). This is just the presentation logic. If you have business logic, put it in a service and separate it out. All this is really doing is collapsing the XAML and presentation logic into one simple place, makes things far easier. And then at the end you realize, that separating XAML and presentation logic, which hides in bindings, properties on the ViewModel, converters etc actually didn’t add anything, they just made life harder.

      1. Wojciech Gebczyk

        1. If you are describing as “state” all those *Model class tree/hierarchy, then this is not the same state as in React/Redux.
        The *Model classes is more JSX after trasformation, than state from which you build your view/view-model.
        I was even thinking about extending C#, to allow define inline XAML snippets like in TypeScript’s jsx (embeddable XML-like syntax).

        When comparing to React/Redux, you omit Store and reducers. In your example Store is kind of represented by set of fields in your *Page class.
        Reducers are embedded in commands or in this Validate method.

        If we would like to have View as “function from state”, then I would start from Redux model with clear store/state AND reducer(s) AND JSX/view-build.

        2. Still not fully understand from what pain you try to relieve the developer.
        Is that less code typing? Clear state handling? Have things related to view in one place?

        3. As I see transition to Redux like approach where unidirectional flow is way of doing things, worth to consider,
        then I think introduction of this *Model as Virtual DOM or “Virtual Visual Tree” does not gives too many benefits to pay off.
        A few reasons:
        – We duplicate existing controls hierarchy. We already have controls, its automation peers and now wewould have VVT Models
        – We add additional layer to retained graphics model
        – As we create whole view on each state change, then how it would affect performance?
        – Does BuildView will render VVT for styles also, or thwy will be externalized?
        – How it would handle view composition?
        – How to handle master-detail?
        – How to handle big collection/data-grid change by moving one item from last position to first (or ordering/sorting/filtering)?
        – How to handle animations, transitions?

        I see some simplification for visual states, etc – so it is not that it adds only problems.
        There is question how much we can gain and how much of existing problems we can solve.

        1. Adam Pedley

          Well that’s a lot of questions πŸ™‚ The state I am referring to is Visual State. I don’t really like the redux approach before I think its too much overhead for such simple changes.

          Redux seems like an architecture solving a problem no one really had.

          This post was mainly a thought experiment on moving to a more Flutter / Reactive style, away from MVVM, to help reduce code and visual state complexity. Its more about removing patterns than adding another one or replacing it.

          XF doesn’t tend will to this approach without the ImmutableUI framework added, as mentioned you don’t want to follow my code examples exactly you want to use the ShadowUI. Flutter works well this this approach because there is only one UI Model.

          ImmutableUI does a differential between the ShadowUI and XF UI so performance wise, nothing much changes. I think many of your questions are assuming that I redo the native UI and apply it every time. This isn’t how it works. I create a new UI model every time, then ImmutableUI does a differential change analysis on the Apply() and then updates the existing UI with the changes.

          So in terms of MasterDetail, animations, collections, nothing much should really change.

  4. David Britch

    Interesting article, with plenty of sentiments I don’t disagree with. However, the one sentiment I do disagree with is:

    “XAML was also meant to be done by designers, then passed to developers to wire up. I have never in my entire professional career ever seen this happen, and for many reasons, can never see it happening.”

    While I agree that the designer-developer workflow maybe unrealistic in mobile scenarios, I used it on every project I worked on as as a WPF and Silverlight developer. So really I think it’s an area where we have different personal experiences.

    1. Adam Pedley

      I understand that things were/are different in WPF, but as you mentioned the designer-developer workflow seems unrealistic in mobile scenarios, and I personally have never come across it (even in non-mobile scenarios, so that is a difference).

      So I think you do actually agree with it πŸ™‚ Just not in context to WPF.

      But this post is exclusively about Xamarin.Forms, and I think some setups/ideas and concepts from WPF and Silverlight made their way across to XF, but shouldn’t have, because XF isn’t setup to handle them.

  5. Muhaymin

    I will put up a scenario which we have in one of the projects

    We have more than 400 views and more than 50-60 controls which we have written in XAML and reuse. (Whatever having more than 2-3 occurrence is implemented or tried to implement as control).

    As the project evolved we have to refactor sometimes, sometimes we don’t do it.

    Now let’s say if I have done the UI code, with very complex nesting and length of pages.

    If it is XAML, the code is more readable than writing the same in C#.

    What’s your view on it, regarding maintaining apps.

    1. Adam Pedley

      The readability of code in C# is the same as XAML. I can point you this project for some awesome bindings that make it even easier. https://github.com/VincentH-Net/CSharpForMarkup

      Reuse of controls would also be easy and similar to XAML.

      XAML is an easy to read markup language, but I don’t think you really lose anything in C# either. If you are using an MVVM architecture though, XAML is a good fit. This approach requires a larger shift in how you develop an app.

      Obviously I wouldn’t recommend rewriting an app from XAML to C#.

      I do think many people confuse writing a C# view as combining all your code together, but your View is still separated from your business logic.