diff --git a/doc/Overview/Mvux/Assets/MvvmWeather.jpg b/doc/Overview/Mvux/Assets/MvvmWeather.jpg new file mode 100644 index 0000000000..d2e6f720ef Binary files /dev/null and b/doc/Overview/Mvux/Assets/MvvmWeather.jpg differ diff --git a/doc/Overview/Mvux/Assets/MvvmWeatherUpdated.jpg b/doc/Overview/Mvux/Assets/MvvmWeatherUpdated.jpg new file mode 100644 index 0000000000..55489f391a Binary files /dev/null and b/doc/Overview/Mvux/Assets/MvvmWeatherUpdated.jpg differ diff --git a/doc/Overview/Mvux/Overview.md b/doc/Overview/Mvux/Overview.md index a53a7da3e6..4149d79ddb 100644 --- a/doc/Overview/Mvux/Overview.md +++ b/doc/Overview/Mvux/Overview.md @@ -4,121 +4,362 @@ uid: Overview.Mvux.Overview # MVUX Overview -**M**odel, **V**iew, **U**pdate, e**X**tended (**MVUX**) is an evolution of the MVU design pattern, that encourages unidirectional flow of immutable data. MVUX supports data binding, bringing together the best of the MVU and MVVM design patterns. +**M**odel, **V**iew, **U**pdate, e**X**tended (**MVUX**) is an implementation of the Model-View-Update design pattern, that encourages the flow of immutable data in a single direction. What differentiates MVUX from other MVU implementations is that it has been designed to support data binding. -MVUX uses a source code generator to generate bindable proxies for each Model. Additional bindable proxies are generated for other entities where needed. -Bindable proxies are used as a bridge that enables immutable entities to work with the Uno Platform data-binding engine. +## Why MVUX? -Changes in the bindable proxies result in parts of the Model being recreated, rather than changing properties. This ensures the Model is immutable, and thus eliminates a large set of potential exceptions and issues related to mutable entities. +To better understand the need for MVUX, let us consider a weather application that will display the current temperature, obtained from an external weather service. At face value, this seems simple enough. All the app has to do is call a service to retrieve latest temperature and display the returned value. -## Learning MVUX by samples +### Weather App Example - MVVM -To better understand MVUX, let us consider a weather application that will display the current temperature, obtained from an external weather service. At face value, this seems simple enough: call service to retrieve latest temperature and display the returned value. +For example, using a Model-View-ViewModel (MVVM) approach, the following `MainViewModel` initializes the `CurrentWeather` property with the information obtained from the weather service. The XAML binds the `CurrentWeather` property of the `DataContext` (an instance of the `MainViewModel`) to the Text property of a TextBlock -Although this seems like an easy problem, as is often the case, there are more details to consider than may be immediately apparent: +# [MainViewModel](#tab/viewmodel) -- What if the external service isn't immediately available when starting the app? -- How does the app show that data is being loaded? Or being updated? -- What if no data is returned from the external service? -- What if an error occurs while obtaining or processing the data? -- How to keep the app responsive while loading or updating the UI? -- How do we refresh the current data? -- How do we avoid threading or concurrency issues when handling new data in the background? -- How do we make sure the code is testable? +```csharp +public partial class MainViewModel : ObservableObject +{ + private readonly IWeatherService _weather; -Individually, these questions are simple enough to handle, but hopefully, they highlight that there is more to consider in even a very trivial application. Now imagine an application that has more complex data and user interface, the potential for complexity and the amount of required code can grow enormously. + [ObservableProperty] + private WeatherInfo? _currentWeather; -MVUX is a response to such situations and makes it easier to handle the above scenarios.  + public MainViewModel(IWeatherService Weather) + { + _weather = Weather; + _ = LoadWeather(); + } -## WeatherApp Sample + private async Task LoadWeather() + { + CurrentWeather = await _weather.GetCurrentWeather(); + } +} +``` +The `ObservableObject` comes from the [`CommunityToolkit.Mvvm`](https://www.nuget.org/packages/CommunityToolkit.Mvvm) package and provides an implementation of the [`INotifyPropertyChanged`](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.data.inotifypropertychanged) interface, which is used to notify the UI when property values change. The `ObservableProperty` attribute (also from the `CommunityToolkit.Mvvm` package) is used to instruct the source code generator to emit properties that include raising the `PropertyChanged` event when the value changes. In this case the `CurrentWeather` property is generated, from the `_currentWeather` field, and will raise the `PropertyChanged` event when the value is set in the `LoadWeather` method. -You can find the code for our weather app here: https://github.com/unoplatform/Uno.Samples/tree/master/UI/MvuxHowTos/WeatherApp +# [MainPage](#tab/page) -## What is MVUX? +```xml + + + + + + + + +``` +The DataContext on the MainPage is set to be an instance of the MainViewModel -MVUX is an extension to the MVU design pattern, and leverages code generation in order to take advantage of the unique data-binding engine of WinUI and the Uno Platform. +```csharp +public MainPage() +{ + this.InitializeComponent(); -### Model + DataContext = new MainViewModel(new WeatherService()); +} +``` +---- +Here's this simple application running: +![Mvvm Weather App](Assets/MvvmWeather.jpg) + + +The code required to call the `GetCurrentWeather` and displaying the resulting `Temperature` using XAML is simple enough. However, there are a few things we should consider: -The **Model** in MVUX is similar in many ways to the viewmodel in MVVM. The **Model** defines the properties that will be available for data binding and methods that include any business logic. In MVUX this is referred to as the **Model**, highlighting that it is immutable by design. +- If the `GetCurrentWeather` method takes a finite amount of time to complete, what should be displayed while the app is waiting for the result? +- If the `GetCurrentWeather` service fails, for example due to network issues, should the app display an error? +- If the `GetCurrentWeather` service returns no data, what should the app show? +- How can the user force the data to be refreshed? -For our weather app, `WeatherModel` is the **Model**, and defines a property named `CurrentWeather`. +The previous example has been updated in the following code to addresses these points. As you can see the updated code is significantly more complex than the original code. + +# [MainViewModel](#tab/viewmodel) ```csharp -public partial record WeatherModel(IWeatherService WeatherService) +public partial class MainViewModel : ObservableObject { - public IFeed CurrentWeather => Feed.Async(this.WeatherService.GetCurrentWeather); -} -``` + private readonly IWeatherService _weather; -The `CurrentWeather` property returns a feed (`IFeed`) of `WeatherInfo` entities (for those familiar with [Reactive](https://reactivex.io/) this is similar in many ways to an `IObservable`). When the `CurrentWeather` property is accessed, an `IFeed` is created via the `Feed.Async` factory method, which will asynchronously call the `GetCurrentWeather` service. + [ObservableProperty] + private WeatherInfo? _currentWeather; -### View + [ObservableProperty] + private bool _isError; + + [ObservableProperty] + private bool _noResults; + + public MainViewModel(IWeatherService Weather) + { + _weather = Weather; + LoadWeatherCommand.Execute(default); + } + + [RelayCommand] + public async Task LoadWeather() + { + try + { + IsError = false; + + CurrentWeather = await _weather.GetCurrentWeather(); + NoResults = CurrentWeather is null; + } + catch + { + IsError = true; + } + } +} +``` +`IsError` and `NoResults` properties are generated from the `_isError` and `_noResults` fields respectively. The `LoadWeather` method now has the `RelayCommand` attribute (also from the `CommunityToolkit.Mvvm` package) which will generate an [`ICommand`](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.input.icommand), `LoadWeatherCommand`. The `LoadWeatherCommand` implementation also includes an `IsRunning` property that returns true when the `ICommand` is being executed. The `IsError`, `NoResults` and `IsRunning` properties are used to control the visibility of the UI elements in the XAML. -The **View** is the UI, which can be written in XAML, C#, or a combination of the two, in the same way that you would if you were using another design pattern. For example, the following can be used to data bind the `Text` property of a `TextBlock` to the `CurrentWeather.Temperature` property. +# [MainPage](#tab/page) ```xml - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +