⬆️ ⬇️

DynamicData: Changing Collections, MVVM Design Pattern, and Reactive Extensions

In February 2019, ReactiveUI 9 was released - a cross-platform framework for building GUI applications on the Microsoft .NET platform. ReactiveUI is a tool for tightly integrating reactive extensions with the MVVM design pattern. You can start familiarizing yourself with the framework from a series of articles on Habré or from the introductory page of documentation . ReactiveUI 9 update includes many fixes and improvements , but perhaps the most interesting and significant change is tight integration with the DynamicData framework , which allows working with changing collections in a reactive style. Let's try to figure out when DynamicData can be useful to us and how this powerful reactive framework is built inside!



Prerequisites



First, let's define the range of tasks solved by DynamicData , and find out what we are not satisfied with the standard tools for working with changing data sets from the System.Collections.ObjectModel namespace.



The MVVM pattern, as is known, implies a division of responsibility between the layers of the model, the presentation and the presentation model of the application. The model layer is represented by domain entities and services, and knows nothing about the view model. The model layer encapsulates all the complex application logic, and the view model delegates model operations, giving the view access to information about the current state of the application through observable properties, commands, and collections. The standard tool for working with changing properties is INotifyPropertyChanged , for working with user actions, ICommand , and for working with collections, for INotifyCollectionChanged and implementing ObservableCollection and ReadOnlyObservableCollection .





')

The implementation of INotifyPropertyChanged and ICommand usually remains on the developer’s conscience and the MVVM framework used, but the use of ObservableCollection imposes a number of restrictions on us! For example, we cannot change a collection from a background thread without Dispatcher.Invoke or a similar call, which could be useful if you work with data arrays that some background operation synchronizes with the server. It should be noted that in the idiomatic MVVM, the model layer does not need to be aware of the GUI application architecture used, and be compatible with the MVC or MVP model, and that is why there are numerous Dispatcher.Invoke controls that allow access to the user interface control from a background thread running in the domain service, violate the principle of division of responsibility between the layers of the application.



Of course, it would be possible to declare an event in the domain service, and pass the chunk with the changed data as the event arguments. Then subscribe to the event, wrap the Dispatcher.Invoke call into the interface, so as not to depend on the GUI framework used, move Dispatcher.Invoke to the view model and change the ObservableCollection needed, however there is a much simpler and more elegant way of solving the indicated range of tasks without writing a bicycle . Let's start the study!



Reactive extensions. Manage data streams



To fully understand the abstractions introduced by DynamicData , and how to work with changing reactive data sets, let's recall what reactive programming is and how to use it in the context of the Microsoft .NET platform and the MVVM design pattern . The way of organizing the interaction between program components can be interactive and reactive. With interactive interaction, the consumer function synchronously receives data from the provider function (pull-based approach, T , IEnumerable ), and during reactive interaction, the provider function asynchronously delivers the data to the consumer function (push-based approach, Task , IObservable ).







Reactive programming is programming using asynchronous data streams, and reactive extensions are a special case of its implementation based on the IObservable and IObserver from the System namespace, defining a series of LINQ-like operations on the IObservable interface, called LINQ over Observable. Reactive extensions support .NET Standard and work wherever the Microsoft .NET platform works.







The ReactiveUI framework invites application developers to take advantage of the reactive implementation of the ICommand and INotifyPropertyChanged , providing such powerful tools as ReactiveCommand<TIn, TOut> and WhenAnyValue . WhenAnyValue allows WhenAnyValue to convert a class property that implements INotifyPropertyChanged to an event stream of type IObservable<T> , which simplifies the implementation of dependent properties.



 public class ExampleViewModel : ReactiveObject { [Reactive] //  ReactiveUI.Fody,  // -  // OnPropertyChanged   Name. public string Name { get; set; } public ExampleViewModel() { //  OnPropertyChanged("Name"). this.WhenAnyValue(x => x.Name) //   IObservable<string> .Subscribe(Console.WriteLine); } } 


ReactiveCommand<TIn, TOut> allows you to work with a command as with an event of type IObservable<TOut> , which is published whenever the command completes execution. Also, any command has a ThrownExceptions property of type IObservable<Exception> .



 // ReactiveCommand<Unit, int> var command = ReactiveCommand.Create(() => 42); command //   IObservable<int> .Subscribe(Console.WriteLine); command .ThrownExceptions //   IObservable<Exception> .Select(exception => exception.Message) //   IObservable<string> .Subscribe(Console.WriteLine); command.Execute().Subscribe(); // : 42 


All this time we have been working with IObservable<T> , as with an event that publishes a new value of type T whenever the state of the object being monitored changes. Simply put, IObservable<T> is a stream of events, a sequence stretched in time.



Of course, we could work with collections just as easily and naturally — whenever a collection changes, publish a new collection with changed elements. In that case, the published value would be IEnumerable<T> or more specialized, and the event itself would be IObservable<IEnumerable<T>> . But, as a critically-minded reader correctly notes, this is fraught with serious problems with the performance of the application, especially if there are not a dozen elements in our collection, but a hundred, or even a few thousand!



Introduction to DynamicData



DynamicData is a library that allows you to use the full power of reactive extensions when working with collections. Out-of-box jet extensions do not provide optimal ways to work with changing datasets, and DynamicData’s task is to fix this. In most applications, there is a need to dynamically update collections — usually, the collection is populated with some items when the application is started, and then updated asynchronously, synchronizing information with the server or database. Modern applications are quite complex, and it is often necessary to create projections of collections — filter, transform, or sort elements. DynamicData was designed to get rid of the incredibly complex code that we would need to manage dynamically changing data sets. The tool is actively developing and refining, and now more than 60 operators are supported for working with collections.







DynamicData is not an alternative implementation of ObservableCollection<T> . The DynamicData architecture is based primarily on domain-specific programming concepts. The ideology of use is based on the fact that you control a certain data source, a collection to which the code responsible for synchronizing and changing data has access. Next, you apply a series of statements to the source, with the help of which you can declaratively transform the data without the need to manually create and modify other collections. In fact, with DynamicData you separate read and write operations, and you can only read in a reactive way - therefore, the inherited collections will always be synchronized with the source.



Instead of classic IObservable<T> , DynamicData defines operations on IObservable<IChangeSet<T>>> and IObservable<IChangeSet<TValue, TKey>> , where IChangeSet is a chunk containing information about the collection change — the type of change and the elements that were affected. This approach can significantly improve the performance of the code for working with collections, written in a reactive style. In this case, IObservable<IChangeSet<T>> can always be transformed into a regular IObservable<IEnumerable<T>> , if it becomes necessary to process all elements of the collection at once. If it sounds difficult - do not worry, from the code examples everything will become clear and transparent!



DynamicData usage example



Let's look at a number of examples in order to better understand how DynamicData works, how it differs from System.Reactive and which tasks common application developers with GUI can help solve. Let's start with a comprehensive example published by DynamicData on GitHub . In the example, the data source is a SourceCache<Trade, long> containing a collection of deals. The task is to show only active transactions, transform models into proxy objects, sort the collection.



 //    System.Collections.ObjectModel, //       . ReadOnlyObservableCollection<TradeProxy> list; //   ,   . //   Add, Remove, Insert   . var source = new SourceCache<Trade, long>(trade => trade.Id); var cancellation = source //      . //   IObservable<IChangeSet<Trade, long>> .Connect() //       . .Filter(trade => trade.Status == TradeStatus.Live) //    -. //   IObservable<IChangeSet<TrandeProxy, long>> .Transform(trade => new TradeProxy(trade)) //    . .Sort(SortExpressionComparer<TradeProxy> .Descending(trade => trade.Timestamp)) //  GUI    . .ObserveOnDispatcher() //    - //    System.Collections.ObjectModel. .Bind(out list) // ,       //    . .DisposeMany() .Subscribe(); 


In the example above, when you change the SourceCache , which is the data source, the ReadOnlyObservableCollection also change accordingly. At the same time, when removing elements from the collection, the Dispose method will be called, the collection will always be updated only in the GUI stream and remain sorted and filtered. Cool, no Dispatcher.Invoke and complex code!



SourceList and SourceCache Data Sources



DynamicData provides two specialized collections that can be used as a variable data source. These collections are the SourceList and SourceCache<TObject, TKey> . It is recommended to use SourceCache whenever the TObject has a unique key, otherwise use SourceList . These objects provide a familiar .NET API for developers to modify data — the Add , Remove , Insert methods, and the like. To convert data sources to IObservable<IChangeSet<T>> or IObservable<IChangeSet<T, TKey>> , use the .Connect() operator. For example, if you have a service that updates a collection of items in the background, you can easily synchronize the list of these items with the GUI, without Dispatcher.Invoke and architectural excesses:



 public class BackgroundService : IBackgroundService { //    . private readonly SourceList<Trade> _trades; //     . //  ,     , //    Publish()  Rx. public IObservable<IChangeSet<Trade>> Connect() => _trades.Connect(); public BackgroundService() { _trades = new SourceList<Trade>(); _trades.Add(new Trade()); //    ! //    . } } 


DynamicData uses the built-in .NET types to display data to the outside world. With the help of powerful DynamicData operators, we can convert the IObservable<IChangeSet<Trade>> into a ReadOnlyObservableCollection our view model.



 public class TradesViewModel : ReactiveObject { private readonly ReadOnlyObservableCollection<TradeVm> _trades; public ReadOnlyObservableCollection<TradeVm> Trades => _trades; public TradesViewModel(IBackgroundService background) { //   ,  ,  //     System.Collections.ObjectModel. background.Connect() .Transform(x => new TradeVm(x)) .ObserveOn(RxApp.MainThreadScheduler) .Bind(out _trades) .DisposeMany() .Subscribe(); } } 


In addition to Transform , Filter and Sort , DynamicData includes a host of other operators, supports grouping, logical operations, collection smoothing, using aggregate functions, excluding identical elements, counting elements, and even virtualization at the presentation model level. You can read more about all the operators in the project's README on GitHub .







Single-threaded collections and change tracking



In addition to SourceList and SourceCache , the DynamicData library also includes a single-threaded implementation of a modifiable collection — ObservableCollectionExtended . To synchronize two collections in your view model, declare one of them as ObservableCollectionExtended , and the other as ReadOnlyObservableCollection and use the ToObservableChangeSet operator, which behaves the same as Connect but is designed to work with ObservableCollection .



 //   . ReadOnlyObservableCollection<TradeVm> _derived; //    -. var source = new ObservableCollectionExtended<Trade>(); source.ToObservableChangeSet(trade => trade.Key) .Transform(trade => new TradeProxy(trade)) .Filter(proxy => proxy.IsChecked) .Bind(out _derived) .Subscribe(); 


DynamicData also supports tracking changes in classes that implement the INotifyPropertyChanged interface. For example, if you want to be notified of a collection change whenever a property of a particular element changes, use the AutoRefresh operator and pass the selector of the desired property with the argument. AutoRefesh and other DynamicData operators will allow you to easily and easily validate a huge number of forms and nested forms that are displayed on the screen!



 //  IObservable<bool> var isValid = databases .ToObservableChangeSet() //      IsValid. .AutoRefresh(database => database.IsValid) //       . .ToCollection() // ,    . .Select(x => x.All(y => y.IsValid)); //   ReactiveUI, IObservable<bool> //     ObservableAsProperty. _isValid = isValid .ObserveOn(RxApp.MainThreadScheduler) .ToProperty(this, x => x.IsValid); 


Based on the functionality of DynamicData, you can quickly create fairly complex interfaces - this is especially true for systems that display a large amount of data in real time, instant messaging systems, and monitoring systems.







Conclusion



Reactive extensions are a powerful tool that allows you to work declaratively with data and user interface, write portable and supported code, and solve complex tasks in a simple and elegant way. ReactiveUI allows developers on the .NET platform to closely integrate reactive extensions into their projects using the MVVM architecture, providing reactive implementations of INotifyPropertyChanged and ICommand , and DynamicData takes care of synchronizing collections by implementing INotifyCollectionChanged , extending the capabilities of reactive extensions and taking care of performance.



ReactiveUI and DynamicData libraries are compatible with all GUI frameworks of the .NET platform, including Windows Presentation Foundation, Universal Windows Platform, Avalonia , Xamarin.Android, Xamarin Forms, Xamarin.iOS. You can start learning DynamicData from the corresponding ReactiveUI documentation page . Also be sure to check out the DynamicData Snippets project, which contains code samples for all occasions.

Source: https://habr.com/ru/post/445098/



All Articles