Nick's .NET Travels

Continually looking for the yellow brick road so I can catch me a wizard....

Redux and the State of My XAML Application (part 3)

This is the final part in this series looking at using Redux in a XAML application. Previous parts:

- Part 1 -

- Part 2 -

At the end of Part 2 I left you wondering what magic I’m using to prevent the entire list being refreshed as items were being added/removed from the Family. In this post we’ll look at what’s going on behind the scenes.

Let’s start with the bolded text from the XAML in Part 2 – I’ve included it here so you don’t need to go back and look.

   <converters:ImmutableDataConverter x:Key="ImmutableDataConverter"/>
<Grid DataContext="{Binding Converter={StaticResource ImmutableDataConverter}}">

So the secret ingredient is the ImmutableDataConverter which takes the current DataContext (which will be an instance of the MainViewModel) and returns an object that will become the DataContext for the rest of the page. The question is, what is this object and what does it do?

If you recall the issue we saw when we didn’t use the ImmutableDataConverter is that when the Data property on the MainViewModel changes (ie raised the PropertyChanged event) every data bound element on the page is refreshed. What we want is that only the elements on the page where data has changed should be updated. To do this, we need to step through the Data object and only raise PropertyChanged for the parts that have actually changed. Based on this description, the ImmutableDataConverter has to have the smarts to a) prevent PropertyChanged causing the entire UI to refresh and b) be able to iterate over every object in the Data object graph and where appropriate raise the PropertyChanged event.

Behind the scenes the ImmutableDataConverter is super simple – all it does is create an instance of the ImmutableDataWrapper<T> class. It uses a small piece of reflection to determine what the generic parameter should be based on the implementation of the IHasImmutableData interface on the MainViewModel.

The ImmutableDataWrapper<T> exposes a single property Data, of type T (and it’s no coincidence that this is the same as the IHasImmutableData<T> interface which also exposes a property Data, of type T – thereby making it simple to add the ImmutableDataConverter without changing any other lines of XAML). It also listens to the PropertyChanged event on the source entity, which in this case is the MainViewModel. Now instead of the PropertyChanged event on the MainViewModel being picked up by the MainPage, it is instead picked up by the ImmutableDataWrapper and used to invoke the ChangeData method where all the work happens.

The ChangeData method is used to compare the old Data object with the new Data object (ie the value that is set on the MainViewModel when the PropertyChanged event is triggered). It does this by using reflection to step through each property on the Data object:

- Properties that are of value type, or string, are updated on the old Data object if the value on the new Data object is different – the PropertyChanged event is raised for just that property.

- For properties that return a non-value type (or string) reflection is used to interrogate the nested entity and work out which properties need to be updated.

- For ObservableCollections some basic list change detection is used to trigger add/remove events on the collection on the old Data object – we can probably improve the logic here to be more efficient but for the moment it does the job.

As you can imagine there’s quite a bit of reflection that has to go on each time the Data object changes. Assuming that the Data object could change quite a bit, we don’t want to be doing reflection every time, which is where the TypeHelper class comes it. The TypeHelper class has some smarts of assisting with both checking to see if an entity has change, and for updating entities. Based on the type of entity, it caches methods that are used for comparison and updating entities. You can check out the TypeHelper class if you want to see more of the details

So lastly, let’s look at the requirements for the ViewModel and your Data entity:

- ViewModel needs to implement IHasImmutableData

- Data entity (and any nested entities) needs to implement INotifyPropertyChanged but also IRaisePropertyChanged – this is required so that the ChangeData method can raise the PropertyChanged on behalf of a data entity

- Properties on the Data entity (and any nested entities) should not raise PropertyChanged – otherwise there will be multiple PropertyChanged events raised

- Any collections within the Data entity hierarchy should use ObservableCollection<T>

A couple of final pointers:

- Currently this is only available for UWP – I need to implement the appropriate converter for Xamarin.Forms (and I guess WPF if anyone cares?)

- Currently this is not thread safe – make sure you update the Data property on the ViewModel on the UI thread.

Comments are closed