GitHub Project: Dotnet-maui-workshop (section 7)
Note: For this fictional application everything can be reused between Monkey and Inventory with the exception of the Population in the detail view - inventory does not have a population.
MAUI and MvpVm
The Model-View-ViewModel (Mvvm) pattern is a great reusable widget pattern, but when it comes to building a framework/application you’ll want to use a more extensible pattern such as the Model-View-Presenter-ViewModel (MvpVm) pattern.
To understand why MvpVm is preferred, you’ll need to know some history, you’ll find that Mvvm has already been a traveled path (with MVC) requiring a new pattern to meet its short comings; thus it evolved to MVP.
Model View Controller (MVC)
The Model View Controller pattern essentially became obsolete with the emergence of smart controls; the "Controller" in MVC was replaced with controls that automatically updated the model via data binding (reference Twisting the Triad articles below)
It wasn't only the lack of a "Controller" that sparked the need for a new pattern, it was also the issues that MVC presented. These were documented in the Taligent paper and Twisting the Triad (below).
Model View Presenter (MVP)
A key problem, that I trust all of us have encountered with the MVVM pattern, is the inability to easily reuse a ViewModel; components become tightly coupled. This, along with other issues, are not new; in Martin Fowlers article on GUI Architectures he addresses the Presentation Model - which is essentially MVVM. Per GUI Architectures, the Presentation Model and Application Model are evolved MVC patterns with access to the View being the distinguishing difference. MVP solved the issues presented by these MVC patterns (read Twisting the Triad and the Taligent paper links above for more info).
Microsoft Practices and Patterns Architecture Guide 2.0 covers, in great detail, best practices and patterns for application design. The emphasis is on MVP as it lends itself to decoupled applications, i.e., the views and view models can truly be reusable. AppArchGuide2.0.pdf (3.09 mb)
MVP as a pattern is the tried and true pattern for applications, established by early architects and emphasized in Microsoft's best practices and patterns. However, the emergence of WPF (and now MAUI) didn't quite fit as-is. The pattern still applies, the only difference is there is now a ViewModel that the View observes. As a result I coined the phrase MvpVm in my MSDN article (click image below).
In MvpVm the View declares the presenter, and the presenter is responsible for populating the view-model and managing the business logic layer. I should emphasize that the presentation layer never accesses the data access layer directly; it only is aware of the business logic layer which in turn is only aware of data layer interfaces. Because of these interfaces and IOC, the application can easily swap out implementation as business logic dictates. For example, in my GitHub Dotnet-maui-workshop (section 7) project, I swap out the offline and online implementation of IDataService; an event driven process that automatically updates if internet is disconnected. This is demonstrated by the animated gif at the top of this article.
Decoupled applications can be difficult to follow if you are not familiar with the practices and patterns of the application framework in question. A knowledge of Inversion of control (IOC) aka Dependency Injection (DI) is a prerequisite. I will cover the basics of this applications pattern below.
Tip: you can look at the MvpVm pattern as a collection of puzzle pieces (reusable components) that the presenter is responsible for managing. It has the primary responsibility for updating the view-model, you'll find that via the PresenterBase class, it invokes commands that can update the view-model as well. This permits view-models and commands to remain decoupled (and reusable).
Note: currently I have not established a database. When I do there will be entity business logic classes that will manage those entities.
A picture says a thousand words
Below I show the two view models that are shared between the Monkey and Inventory views. At a glance you can see why they can be easily reused. For the DetailViewModel you'll note there is an IsPopulationVisible flag, the Inventory view does not require the population information; this flag drives its visibility (I point to this in the animated gif at the top of this article).
If we look at our MonkeyPresenter below, used by the MainPage, you'll see that the SetSupportedButtons() method defines the commands (buttons) that we want to display (pointed to below, order counts). Ideally, we don't want to use "magic strings", however since I do not have a reference to the "GotoInventoryCommand" (which resides in a separate DLL) I have to use one. The framework displays the buttons as shown in bottom left pane.
Each command represents a clear separation of concerns with the logic being fully encapsulated within it. Where the buttons "can be" specified in the SetSupportedButtons() method, it is not required, just convenient. In the Inventory view you'll note (in animated gif above) that there is a button with a question mark - this button is set in XAML (there is no command for it). Above I show that the label is handling clicks and they are handled in the OnButtonClickedHandler (green arrow above).
The MainPage view shown below
Note: the flexLayout control handles the display of all commands that are supported by this presenter, e.g., Find Closest, Get data, and Inventory; they are handled by the framework in PresenterBase.
Note in the FindClosestCommand that the "ButtonText property is set to "Find Closest" which is displayed in the first button.
Pointed to above you’ll see the command has access to the view-model, it not only has access to it but also the presenter and view(s). The ButtonEventArgs is set [by the framework] with the following:
This effectively gives the command access to everything the presenter has access to.
With this basic understanding of the application's framework you should be able to quickly navigate its code by going to the presenter and/or command for the applicable process.
The Monkey and Inventory apps share most, if not all, of the currently available components (commands and view-models). We can easily swap out data access layers (offline and online) for both Monkey and Inventory using these reusable components.
With only the presenters being tightly coupled to available components, view-models as well as views can be easily reused.