Reactive UI / Blazor: Why is a View that inherits from ReactiveInjectableComponentBase<T> rendered twice? - mvvm

When I inherit a View from ReactiveInjectableComponentBase<T> the body of the View will be "rendered" twice. I noticed this because a #foreach loop was executed twice on activation of the view. This effect happens no matter how simple the View and the ViewModel are.
Simple reproduction repo: https://github.com/Lukzy/ReactiveUI.ExecutedTwiceBug
Bug filed on ReactiveUI GitHub: https://github.com/reactiveui/ReactiveUI/issues/3256
I used the following code to simulate the behavior.
ViewWithoutInherits: "View rendered" will be executed once.
#page "/whatever"
#using System.Diagnostics
#{
Trace.TraceInformation("View renderer");
}
ViewWithInherits: "View rendered" will be executed twice.
#page "/whatever"
#using System.Diagnostics
#inherits ReactiveInjectableComponentBase<ViewModel>
#{
Trace.TraceInformation("View renderer");
}
ViewModel
using ReactiveUI;
public class TestViewModel : ReactiveObject
{
}
Am I missing something or is this a bug? Any help or hint is highly appreciated.
UPDATE #1:
I replaced the trace code with the suggestion from Dylan Barquilla.
#code {
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
Trace.TraceInformation("View renderer in FIRST RENDER");
}
Trace.TraceInformation("View renderer");
base.OnAfterRender(firstRender);
}
}
Output from ViewWithoutInherits:
View renderer in FIRST RENDER
View renderer
Output from ViewWithInherits:
View renderer in FIRST RENDER
View renderer
View renderer
UPDATE #2:
ViewWithInherits: foreach-loop will be executed twice.
#page "/whatever"
#inherits ReactiveInjectableComponentBase<ViewModel>
#foreach (var value in ViewModel.Values)
{
<p>#(value)</p>
}
ViewWithInject: foreach-loop will be executed once.
#page "/whatever"
#foreach (var value in ViewModel.Values)
{
<p>#(value)</p>
}
#code {
[Inject]
public TestViewModel ViewModel { get; set; }
}
ViewModel
using ReactiveUI;
public class TestViewModel : ReactiveObject
{
public List<string> Values{ get; set; } = new()
{
"a",
"b"
};
}

EDIT 2: after cloning the repo, to prove what I want to say.
I just added a button on both components, with a simple handler (with no real effect).
<button #onclick="#OnClickHandler">Click on me</button>
//-- CODE PART --
public string Test { get; set; } = string.Empty;
private void OnClickHandler()
{
Test = "New";
}
You will see that every time you click on the button, the Foreach loop was executed [N] times will be updated with a two increments.
It is a perfect normal behavior in Blazor. A component being re render is a normal thing, and happen A LOT of time, basically every UI interaction can force the component to re render. So it should not cause you trouble, and Blazor has some useful methods (SetParametersAsync or OnInitialized) that can be used to apply logic only once, not every time the component is rendering.
If a component is inherit of another component, it could be the parent to cause this re rendering, by apply some logic (adding fields, updating fields etc.).
EDIT: I just checked the new outputs that you get, and I think the explanation is that the library that you use update or inject something in the component, after its first rendering, for example adding some fields on it or anything. It is a classic behavior with some other Blazor libraries, so I guess it is pretty normal - but you can ask in the Github / forum of the library you use to be sure.
So it basically means that if you want your code do be executed only once, I suggest you to use the following:
#code {
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
// Do whatever you need here, it will be executed only once
}
base.OnAfterRender(firstRender); // this line is important for Blazor lifecycle
}
}

Related

Best practice MVVM navigation using Master Detail page?

I want to follow the MVVM pattern as much as possible, but I don't know if I am doing the navigation quite well. Note that I am using a MasterDetail page and I want to maintain the Master page, changing only the Detail side when I navigate.
Here is the way I navigate from my ViewModel. In this example, from ViewModelOne to ViewModelTwo:
public class ViewModelOne : ViewModelBase
{
private void GoToViewTwo()
{
var viewTwo = new ViewTwo(new ViewModelTwo());
((MasterView)Application.Current.MainPage).NavigateToPage(viewTwo);
}
}
MasterView implementation:
public class MasterView : MasterDetailPage
{
public void NavigateToPage(Page page)
{
Detail = new NavigationPage(page);
IsPresented = false;
}
}
ViewTwo implementation:
public partial class ViewTwo : PageBase
{
public MenuView(ViewModelTwo vm)
: base(vm)
{
InitializeComponent();
}
}
PageBase implementation:
public class PageBase : ContentPage
{
public PageBase(ViewModelBase vmb)
{
this.BindingContext = vmb;
}
}
Is this the best approach (and best performance) for do the navigation? When I do some navigations, the app starts to run slower and maybe there is something I am not doing fine.
Is this the best approach for do the navigation showing always a MasterDetail page?
Thanks.
I think you're certainly on the right track, however there are a few issues here:
Firstly, you should not be instantiating Views in your view model. As soon as your View Model becomes aware of the view, then you've pretty much broken the pattern.
var viewTwo = new ViewTwo(new ViewModelTwo());
Your view creation should be the responsibility of the master view. In fact, you don't even need to worry about creating views, as you can use a DataTemplate for that. I'll explain that later.
Firstly, we need to separate your View Models from the Views, here is what I propose:
You'll need some kind of base class or interface for your view models in order to keep things generic, you'll see why in a moment. Let's start out with a simple example:
public abstract class ViewModel : INotifyPropertyChanged
{
public event EventHandler OnClosed;
public event EventHandler OnOpened;
//Don't forget to implement INotifyPropertyChanged.
public bool IsDisplayed { get; private set; }
public void Open()
{
IsDisplayed = true;
//TODO: Raise the OnOpened event (Might be a better idea to put it in the IsDisplayed getter.
}
public void Close()
{
IsDisplayed = false;
//TODO: Raise the OnClosed event.
}
}
This of course is a very simple base view model, you can extend on this later, the main reason for this is to allow you to create a master view model which will be responsible for displaying your current page. Here's a simple example of a master view model:
public class MasterViewModel : INotifyPropertyChanged
{
//Don't forget to implement INotifyPropertyChanged.
public ViewModel CurrentPage { get; private set; }
public MasterViewModel()
{
//This is just an example of how to set the current page.
//You might want to use a command instead.
CurrentPage = new MovieViewModel();
}
//TODO: Some other master view model functionality, like exiting the application.
}
Please note that INotifyPropertyChanged would probably be better in some kind of base class, instead of having to re-implement the same code over and over.
Now the MasterViewModel is pretty simple, it just holds the current page, however the purpose of having the master is to allow for application level code to be executed, like closing the app, that way you're keeping this logic away from your other view models.
Right, now onto the good stuff.
Your detail has a relationship to it's parent, therefore it would make sense to say that it is the responsibility of the parent to manage it. In this case, your master-detail view model would look something like this:
public class MovieViewModel : ViewModel
{
protected PickGenreViewModel ChildViewModel { get; private set; }
public MovieViewModel()
{
ChildViewModel = new PickGenreViewModel();
//TODO: Perhaps subscribe to the closed event?
}
//Just an example but an important thing to note is that
//this method is protected because it's the MovieViewModel's
//responsibility to manage it's child view model.
protected void PickAGenre()
{
ChildViewModel.Open();
}
//TODO: Other view model functionality.
}
So, now we've got some kind of view model structure here, I bet you're asking "What about the views?", well, that's where the DataTemplate comes in.
In WPF, it's possible to assign a view to a Type, for example, you can assign the MovieView to the MovieViewModel in XAML, like this:
xmlns:Views="clr-namespace:YourNamespace.Views"
xmlns:ViewModels="clr-namespace:YourNamespace.ViewModels"
...
<DataTemplate DataType="{x:Type ViewModels:MovieViewModel}">
<Views:MovieView/>
</DataTemplate>
Ok great!, now to get the Master View to actually display the current page's view, you simply need to create a ContentPresenter, and bind it's Content to the CurrentPage. Your Master View will look something like this:
<Window
...
xmlns:ViewModels="clr-namespace:YourNamespace.ViewModels">
<Window.DataContext>
<ViewModels:MasterViewModel/>
</Window.DataContext>
<Grid>
<ContentPresenter Content="{Binding CurrentPage}"/>
</Grid>
To extend this further, it's not only the MasterView that needs to contain a ContentPresenter for it's child, it is also the MovieView which needs one for it's child PickGenreViewModel. You can use the same method again:
<Grid>
<!-- The main view code for the movie view -->
...
<Border Visibility="{Binding ChildViewModel.IsDisplayed, Converter=...">
<ContentPresenter Content="{Binding ChildViewModel}"/>
</Border>
</Grid>
Note: Use a boolean to Visibility converter to determine whether to display the child content.
Using this method you don't have to worry about instantiating any views, as the DataTemplate and ContentPresenter handles that for you, all you need to worry about is mapping the view models to the appropriate view.
Phew! That was a lot to take in.
The main points to take away from this are:
You shouldn't be creating views in your view models, remember, UI is UI, Data is Data.
View model responsibilities lie with whoever owns them, for a parent-child relationship, it makes sense to let the parent manage the child, as opposed to a view model manager.
A final note is that there are certainly more than one other ways of achieving this, as I just mentioned, some kind of view and view model manager to be responsible for creating/removing views and view models.

How to bind view model to a UserControl in MVVMCROSS?

I am using the excellent Mvvmcross and Ninja Coder for Mvvmcross for building a cross platform app. For my windows store app I have created a view and a view model using Ninja coder. I have also created a UserControl which will be referenced in the view. Hence I need to bind the same viewmodel to the User control also. I have been trying to set the Data context of the user control to the singleton instance of viewmodel. I have set the data context of the user control like below.
public sealed partial class SearchResultsGridViewControl : UserControl
{
private SearchresultsViewModel _viewModel;
public SearchResultsGridViewControl()
{
this.InitializeComponent();
_viewModel = Mvx.IocConstruct<SearchresultsViewModel>();
this.DataContext = _viewModel;
}
}
But when I refer this User Control in my main view, it throws an error in XAML saying "Object Reference not set to an instance of an object. Cannot create an instance of SearchResultsGridViewControl".
This is my viewmodel:
public class SearchresultsViewModel : BaseViewModel
{
private ISearchResultsService _searchResultsService;
public SearchresultsViewModel(ISearchResultsService searchResultsService)
{
_searchResultsService = searchResultsService;
var items = _searchResultsService.DisplaySearchResults();
SchoolDetails = new ObservableCollection<School>(items);
}
private ObservableCollection<School> _schoolDetails;
public ObservableCollection<School> SchoolDetails
{
get { return _schoolDetails; }
set
{
_schoolDetails = value;
RaisePropertyChanged(() => SchoolDetails);
}
}
public ICommand RefineCommand
{
get
{
refineCommand = refineCommand ?? new MvxCommand(FilterSearchResultsBasedOnRefine);
return refineCommand;
}
}
public void FilterSearchResultsBasedOnRefine()
{
SchoolDetails = new ObservableCollection<School>(_searchResultsService.FilterSchoolsBasedOnRefine(MidDayMeals, PlayGround, DigitalClassroom, DayBoarding, TransportationFacility));
}
}
The grid view in my usercontrol is getting populated when it loads for the first time. But when RefineCommand is called to update the collection from the main view, the grid view in usercontrol is not getting updated. And I am guessing its because of that error earlier in setting the data context of the user control to view model. Please let me know what could be going wrong. I have been banging my head about it for days.
I've been using MVVMCross with Windows Store fairly recently. Without looking back at my code, I'm pretty sure that the Datacontext will inherit from it's parent unless overridden.
So as long as the MvxPage that you have presented has a viewmodel, any user control that you add to it, either in XAML or in code-behind, should share the same data context. If you are looking at doing some MVVMCross data-binding from the User Control, you should probably make sure your User Control implements IMvxStoreView, and ensure that the ViewModel property is set to the value of DataContext.
Hope that help.
Cheers,
Tristan
I think your first problem "Object Reference not set to an instance of an object" is a design time only issue - because you are trying to set the viewmodel using Mvx. at design time. You can workaround this issue if you want to by using design time viewmodels and possibly also by using one of the design time helpers (see https://github.com/MvvmCross/MvvmCross/blob/v3.1/CrossCore/Cirrious.CrossCore.Wpf/Platform/MvxDesignTimeHelper.cs).
I've no idea what your second problem is "The grid view in my usercontrol is getting populated when it loads for the first time. But when RefineCommand is called to update the collection from the main view, the grid view in usercontrol is not getting updated" - this sounds like an issue either in your xaml or in the results returned from FilterSearchResultsBasedOnRefine. From the current level of detail, I can't see what it is. My "gut feeling" is that the problem won't be MvvmCross specific - it'll just be a general Mvvm/data-binding issue.

MvvmCross navigation on screen

Our designer created a layout something like the screen above. The main idea was to create an application with only one screen, just the red part of the screen is changing (i.e. 2 textbox instead of 1 textbox) when you tap on a button. This application will be a multiplatform application and I'm using MvvmCross to create it. My question is that how can i achieve this behavior in Mvvm? My first thought was sg. like the code below, but I'm not satisfied with this solution. Do you have any better solution to this problem? Should i somehow overwrite default navigation on ShowViewModel()?
public class MainViewModel : MvxViewModel
{
private MvxViewModel _currentViewModel;
public MvxViewModel CurrentViewModel
{
get { return _currentViewModel; }
set { _currentViewModel = value; RaisePropertyChanged(() => CurrentViewModel); }
}
public MainViewModel()
{
CurrentViewModel = new DefaultViewModel();
}
public void OnButtonClick()
{
CurrentViewModel = new SecondViewModel();
}
}
public partial class MainViewModel : MvxViewController
{
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
FirstViewModel.WeakSubscribe(ViewModelPropertyChanged);
}
private void ViewModelPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == "CurrentViewModel")
{
if (Model.CurrentViewModel != null)
{
if (Model.CurrentViewModel is SecondViewModel)
{
//remove bindings
//change View
//bind new viewmodel
}
}
}
}
The alternatives for this kind of 'non-page navigation' are similar to those in MvvmCross Dialog:
You can:
Customize the MvxPresenter to allow ShowViewModel to be used
Put a special interface in the Core project and use Inversion of Control to inject the implementation from the UI project to the Core project
Use the MvxMessenger plugin and share messages between the Core and UI project which trigger this type of navigation.
Use a property with a special interface (like IInteractionRequest) on the ViewModel - that property will fire an event when the UI needs to change.
Personally, for your situation, I quite like the first of these options - intercepting ShowViewModel using a presenter.
One other alternative which I might consider is to use some kind of 'Adapter-driven' control which could very easily update it's child contents based on the CurrentViewModel property. On Android, this would be as easy as using an MvxLinearLayout with an adapter. On iOS, however, I think you'd have to write something new to do this - just because iOS doesn't really have a LinearLayout/StackPanel control.

MvvmCross: Android custom webview urls handlers

My Android application had some complex text with hyperlinks embedded inside. The easiest way for me to handle this was to just use an embedded WebView and detect hyperlink clicks to perform custom commands. I am trying to do this the MvvmCross way. Is there a binding available for WebView.SetBackgroundColor or WebView.LoadData? Once I get my custom html inside and the user clicks on hyperlinks, I have used WebView.SetWebViewClient to install my own client which can detect hyperlink clicks and perform custom actions. Any way to turn all of that into proper MvxCommand usage?
Is there a binding available for WebView.SetBackgroundColor
For View color binding see the sample: https://github.com/slodge/MvvmCross-Tutorials/tree/master/ValueConversion
or WebView.LoadData?
See Dynamic Binding UIWebView in MVVMCross
I have used WebView.SetWebViewClient to install my own client which can detect hyperlink clicks and perform custom actions. Any way to turn all of that into proper MvxCommand usage?
It's not clear to me what your question is. Maybe try coding something first and then coming back with some sample code for the problem - eg come back with a specific question about a specific ViewModel so that others can try to answer at a code level instead of at this more general level. Perhaps also try asking one question at a time and asking with a deeper level of detail - How to ask may help you get better results - see https://stackoverflow.com/questions/how-to-ask
I created a custom WebView by inheriting and added Command properties. I then added a custom WebViewClient to detect certain URL's and to call the corresponding command that was bound. Is this a good way to accomplish this?
public class AboutWebView : WebView
{
public IMvxCommand AboutCommand { get; set; }
public IMvxCommand LicenseCommand { get; set; }
public IMvxCommand PrivacyCommand { get; set; }
public AboutWebView (Context ctx,IAttributeSet aset) : base(ctx,aset)
{
SetWebViewClient (new AboutWebViewClient(this));
}
private class AboutWebViewClient : WebViewClient
{
private AboutWebView _parent = null;
public AboutWebViewClient(AboutWebView parent)
{
_parent = parent;
}
public override bool ShouldOverrideUrlLoading (WebView view, string url)
{
if (url.StartsWith ("about://"))
_parent.AboutCommand.Execute ();
else if (url.Equals (App.LICENSE_URL))
_parent.LicenseCommand.Execute ();
else
_parent.PrivacyCommand.Execute ();
return(true);
}
}
}

calling a child page component from parent page component in wicket

I have a problem which I tried to explained in the Image.I hope that will help all to understand what I need.
My Base Page is like this (menuNavPanel is the tree panel):
<div class="colContainer">
<div class="leftColumn" >
<div wicket:id="menuNavPanel"></div>
</div>
<div class="rightColumn">
<wicket:child/>
</div>
</div>
And Ny BIA Page which is a child of Base Page is like this:
<wicket:extend>
<div wicket:id="bodyPanel"></div>
</wicket:extend>
in my Tree Panel, when I click on a node the code is this:
#Override
protected void onNodeLinkClicked(AjaxRequestTarget target, TreeNode node) {
super.onNodeLinkClicked(target, node);
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)node;
Unit unitObject =(Unit) treeNode.getUserObject();
// I want to call bodyPanel fo child page passing the unitObject param
}
Now, How can I call bodyPanel fo child page passing the unitObject param from the tree panel of the parent page?
Am I been able to express my problem? Hoping to get some help :)
Instead of doing the override method, upgrade to Wicket 1.5 and utilize the new event bus to communicate between your components. You can create a custom, type-safe, event that is specific to your component's use case: for example "ItemAddedToShoppingCart" or "GlobalThermoNuclearWarStarted".
The linked article in the 1.5 migration guide provides enough information on how to set up things.
I'm not sure I understand que question correctly. Your BasePage defines a left column with the TreePanel and lets subclasses expand themselves inside the right column div. You usually put a BodyPanel inside BasePages's subclasses. And now you want to invoke a BodyPanel's method on some event on the TreePanel.
You could do it with an overridable method on BasePage, which would be called in TreePanel through getPage(). Your child pages would override that method, and its implementation would call the BodyPanel they're holding.
public class BasePage ... {
// Hook
public void treePanelSelected(Object someObject) { }
...
}
public class ChildPage extends BasePage ... {
BodyPanel bodyPanel;
#Override
public void treePanelSelected(Object someObject) {
bodyPanel.selectionChanged/(someObject);
}
...
}
public class TreePanel ... {
...
#Override
protected void onNodeLinkClicked(AjaxRequestTarget target, TreeNode node) {
super.onNodeLinkClicked(target, node);
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)node;
Unit unitObject =(Unit) treeNode.getUserObject();
((BasePage)getPage()).treePanelSelected(unitObject);
}
}
From my ignorance on your specific needs and details of implementation, I don't see why is subclassing the BasePage necessary. You could add the BodyPanel right there in the BasePage and control it from the same class.
Thanks all, After reviewing all the nice options I finally opted out for the event bus way defined by martijn. What I did is I have created an event payload and connected the panels for the talking. I also needed to pass the selected Id / entity to the receiving panel.
Is there a way to set a compound property model of the receiving panel according to the model of the tree element so that I don't need to do the model manually ?
I did like this for the time being:
public class TreeNodeClickUpdate {
private final AjaxRequestTarget target;
private final long selectedId;
/**
* Constructor
*
* #param target
* #param selectedId
*/
public TreeNodeClickUpdate(AjaxRequestTarget target, long selectedId)
{
this.target = target;
this.selectedId = selectedId;
}
/** #return ajax request target */
public AjaxRequestTarget getTarget()
{
return target;
}
public long getSelectedId() {
return selectedId;
}
}
On the sender side I've done like this:
send(getPage(), Broadcast.BREADTH,
new TreeNodeClickUpdate(target, unitObject.getId()));
And on the receiving end I got it like this:
#Override
public void onEvent(IEvent<?> event) {
super.onEvent(event);
if (event.getPayload() instanceof TreeNodeClickUpdate)
{
TreeNodeClickUpdate update = (TreeNodeClickUpdate)event.getPayload();
setSelectedId(update.getSelectedId()); //sets to id field of panel
update.getTarget().add(this);
}
}
and for just as an example in my receiving panel, to get the value I have created a label like this:
label = new Label("label",new PropertyModel<BiaHomePanel>(this,"selectedId"));
Later, in reality I want to get information from the entity and show in form. Is there a nice way to pass models in a better way or I should pass as a parameter in event payload.
There are two ways to do this. One is cleaner, but requires more code. The other is quick and dirty.
Method 1: (Good)
Since your parent page is being extended, you can provide an abstract method in the parent like
protected abstract WebMarkupContainer getBodyPanel();
that is implemented in your child page and returns the appropriate panel. Then, you can call that method from the panel in your parent page. This is similar to the overrideable method suggested by the other user.
Method 2: (Bad)
The Wicket Component Hierarchy is shared between the parent and child pages. So, if you make sure that your bodyPanel has a unique wicketId and is added directly to the root of the page, you can probably just call
get("bodyPanelId");
and it will return the proper panel.
When I was facing the problem, I thought of two ways to solve this (pre 1.5):
a) implement a variation of the observer pattern to notify other component of events like outlined here: Realising complex cross-component ajax actions in wicket - The observer way
b) using wicket visitors to traverse the component tree doing the same.
I decided to go for variant a) but this introduces coupling from your component to your page-implementation which leads to problems when testing panels on their own. So maybe b) might be the better idea but since my application is running quite smoothly with a) implemented and the next big step will be switching over to 1.5 and the event bus, I haven't yet tried b).