I have a question about the Navigation Service introduced in MvvmCross 5.
In Version 4:
I navigate with ShowViewModel<ViewModel>() to a Fragment
then Init method of the ViewModel is called
after that the OnCreateView method of the Fragment is called
There I can manipulate the view based on ViewModel data (for example add specific elements to the view).
In Version 5:
I navigate with await NavigationService.Navigate<ViewModel>()
the OnCreateView of the Fragment is called first
after that the Initialize method from the ViewModel.
This ends in no ViewModel data while creating the Fragment view.
Is this a bug or a feature of async navigation?
If that is so wanted, is there a better way to manipulate the Fragment view based on ViewModel data?
Is this a bug or a feature of async navigation?
It was by design, but has since (v5.0.4) been revised, see below of flow changes.
If that is so wanted, is there a better way to manipulate the Fragment
view based on ViewModel data?
Using v5.0.4+ should yield the desired behaviour you are expecting. Where the navigation service is awaited on Initialize() of your ViewModel to complete before starting the views life cycle events.
MvvmCross v5.0.0 - v5.0.3
The behaviour you are seeing was present in MvvmCross 5.0.0-5.0.3. The flow was as follows:
ViewModel.Ctor
(Selected Navigate calls) Init(parameter) (deprecated, uses reflection, rather use type safe Initialize)
(Selected Navigate calls) ViewModel.ReloadState(savedState)
(Selected Navigate calls) ViewModel.Start()
BeforeNavigate (NavigationService Event)
*ViewDispatcher.ShowViewModel() (Triggers view life cycles)
*ViewModel.Initialize()
AfterNavigate (NavigationService Event)
BeforeClose (NavigationService Event)
ViewDispatcher.ChangePresentation()
AfterClose (NavigationService Event)
MvvmCross v5.0.4+
v5.0.4+ has improved the flow and changed the navigation order:
ViewModel.Ctor
BeforeNavigate (NavigationService Event)
*ViewModel.Initialize()
Init(parameter) (deprecated, uses reflection, rather use type safe Initialize)
ViewModel.ReloadState(savedState)
ViewModel.Start()
*ViewDispatcher.ShowViewModel() (Triggers view life cycles)
AfterNavigate (NavigationService Event)
BeforeClose (NavigationService Event)
ViewDispatcher.ChangePresentation()
AfterClose (NavigationService Event)
Additional Information
You can check out the GitHub issue(#1968) logged around the navigation order. Additionally, you can check out the pull request(#1971) which updated the Initialize order for version 5.0.4.
Related
I have an Xamarin iOS app where I am using MVVMCross v3.2.1 to control the navigation between the view controllers / view models. I have used the ShowViewModel<TViewModel>(); method to navigation between view models and have a special case where I want to navigate back one step on the navigation stack.
I can do this my using the MvxClosePresentationHint as in ChangePresentation(new MvxClosePresentationHint(this)); but when it navigates back to the previous view I need the data to refresh.
Are there any MVVMCross view model lifecycle methods I can us to detect the back navigation or should I implement a MvxMessage?
As Cheesebaron suggested I am refreshing the data from the ViewWillAppear on the previous ViewController which seems to have resolved the issue.
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
Refresh();
}
I have problem how to implement sub-page navigation in UWP. The page is in RootFrame, which I can use on navigation. But i want to use something like this:
<Page>
<Grid>
<Frame x:Name="MyFrame"/>
</Grid>
</Page>
What I want is, use Navigate method of control MyFrame in ViewModel. I can call the method from code-behind, but I'm developing my app using MVVM. I'm not sure, if Template10 can work with sub-frames.
I appreciate any advice.
EDIT:
More details:
I have pivot control which is in page. the pivot has 2 tabs (pivotitems). The content of the pivotitem must be navigable. What I mean: I pivotitem 1, I need to have one Frame and use it for navigation in the pivotitem. My problem is, how to use or how to call the frame in pivotitem from ViewModel, especially I need to call Navigate method. Now I'm using Template10's navigation service and it's working with rootframe. I don't know, how to use it for other let's say sub-frames.
You can always do this.
var nav = Bootstrapper.NavigationServiceFactory(BackButton.Attach, ExistingContent.Exclude, this.Frame);
This will give you a navigation service for the frame in your page. You can then use session state, if you like.
Bootstapper.SessionState["MyNav"] = nav;
From here your view-model can access the service and navigate. You can repeat this for as many frames as you have. And you can then handle navigation in your view-model without consideration of "where" the frame is, just that your logic requires it to nav.
Does this make sense?
I don't know how you are going to trigger the navigation change so I'll assume it will start from a button click. I am also assuming the button's Command property is already bound to an ICommand in the viewmodel (the same concepts can be applied to different kinds of views).
All we have to do now is to make the ICommand implementation call our custom NavigationService to perform the content switch. This NavigationService class will be nothing but a simple proxy to the window global frame. Its main navigation method can be as simples as:
public void Switch()
{
var rootFrame = Window.Current.Content as Frame;
if ((rootFrame.Content as ParentPage) != null)
{
rootFrame.Navigate(typeof(ChildPage));
}
}
So you have tagged this with Template10 but it seems to be a more general question for UWP as a whole. I wonder if you have considered all of the inherent complexities with this approach - specifically related to suspension and resume. For each frame you have, you would need to save and restore navigation state, which isn't straight-forward when you have nested frames. Have you also considered how global navigation would work?
Template 10 does support the concept of multiple NavigationServices and, therefore, multiple frames, but only from the perspective of you can create them. Template10 does not inherently understand how such frames may be related to each other, so cannot perform automatic back propagation where you have something like:
FrameA[Main->Page1->Page1:Pivot1.FrameB[View1->View2->View3]]
Here we have two frames - FrameA and FrameB. FrameA has navigated from
Main to Page1. Page1 has a Pivot that hosts FrameB in PivotItem1 and
FrameB has navigated from View1 to View 2 and from View2 to View 3.
Global navigation (i.e. the shell back, etc.) would be automatically wired to FrameA, so you would need to intercept that action, and then handle you own navigation activity for FrameB.
Take a look at the BackButtonBehavior to see how it is possible to intercept the global back and then put in place your own action.
I don't know if you can do something like that..
One possible workaround is to use a Messenger that sends a message from your viewmodel to the view's code behind.. I'm not a fan of this solution though, because as I said before you have to use the page's code behind..
I use an observable collection with a selecteditem (name + detail) to push a new contentpage in my navigation and in this new page i modify the name of this selected item but in an other list.
I would like to refresh the data in the observable collection with this other list (saved in an internal storage)
So, can i use an event to notify the previous viewmodel than i push the back button and if it possible which event can i use?
Xamarin.Forms.Page
//
// Summary:
// When overridden, allows application developers to customize behavior immediately
// prior to the Xamarin.Forms.Page becoming visible.
//
// Remarks:
// To be added.
protected virtual void OnAppearing();
This may be helpfull you'll need to override it in your page code.
Overriding the OnAppearing method seems the simplest way to achieve this. This way you'll keep the logic to refresh the data in the page that belongs with the viewmodel that needs to be refreshed. When the page re-appears, it can trigger some logic in the ViewModel to refresh the ObservableCollection.
Another option is to use the MessagingCenter that comes with Xamarin.Forms: https://developer.xamarin.com/guides/xamarin-forms/messaging-center/ This allows pub/sub style messaging between components while staying loosely coupled. You could have the class that manages the internal storage publish messages whenever the list in the gets updated and broadcast this to ViewModels that have subscribed to these updates.
In this particular case, overriding OnAppearing seems the simplest solution though.
I am trying to program in MVVM and I have the following use case:
A TextBox's text is bound to a property in the VM
A Button is command bound to a relay command
When the user presses the Button, the web browser's Navigate(url) method is called with the URL being the text in the TextBox
Above is the use case I want to create, but 1 and 2 is possible using the MVVM design pattern, but I could not find an adequate way to invoke the browser's Navigate() method. First of all, is it possible to call a method of a control from VM (please let me know if there is a way)? And in the above use case, what would be the appropriate way to structure the program if it is not possible?
Thanks
You could do the following:
Add a property MyUrl to your ViewModel
Bind MyUrl to your WebBrower's Source property
Make sure the property implements INotifyPropertyChanged. Then your Xaml:
<WebBrowser Source="{Binding MyUrl}" />
What if you REALLY wanted to call a UI method from the ViewModel?
If you ever do run into a situation where you absolutely need to call a method on a UI control for instance, you can hook up events on the ViewModel and then your UI registers to this event and does something UI specific...
VM code...
//... some VM logic
EpicNavigateEvent(url) // raise event, allowing UI to handle how
In your code-behind on your view (this is the part where some MVVM purests freak), you could register the event:
myVm.Navigate += doSomeNavigation;
...
public void doSomeNavigation(string url)
{
// call Navigate
}
I've successfully used this approach for applications where we have a single ViewModel layer and multiple technologies hooked up the views (WinForms, WPF and Asp.Net).
If you're looking for something more elegant, have a look at the User Interaction Patterns on MSDN.
The concept is the same though: Call something on the VM and the View is handles it appropriately.
Common scenarios for this type of approach is want to show a message to the user from the VM. Your VM should raise an event saying: ShowMyMessage("You're awesome"), then your UI is notified and handles it: MessageBox.Show(msg) or whatever.
As long as you stick to there rules you should be golden:
ViewModels should NOT be concerned about UI code
Views must ONLY handle the presentation of the data provided by your ViewModels.
Don't overcomplicate it. KISS...
In the MvvmCross N=26 tutorial, dynamic fragments are loaded into a frame via button click event in the View (code snippet below). However, I'm trying to figure out how handle the click event in the ViewModel and not in the View. After the button is clicked, how do I know the button was clicked and in the View, load the correct fragment in the frame?
For instance, I may have 10 fragments and one frame in the FirstView xml. I want to be able to load any of those 10 fragments in that frame based on a property of a object referenced in the FirstViewModel. Can I check that property in the View and load the fragment that I want from the 10 fragments available? (i.e. remove the but1.Click event in the View and still run the transaction based on the value of the object in the ViewModel)
but1.Click += (sender, args) =>
{
var dNew = new DubFrag()
{
ViewModel = ((SecondViewModel) ViewModel).Sub
};
var trans3 = SupportFragmentManager.BeginTransaction();
trans3.Replace(Resource.Id.subframe1, dNew);
trans3.AddToBackStack(null);
trans3.Commit();
};
The approach you suggest of mapping a vm property to which fragment to show should work, yes.
To use this, just subscribe to property changed in your view code (there are some weak reference helper classes and extension methods to assist with this)
Alternatively, this blog post - http://enginecore.blogspot.ca/2013/06/more-dynamic-android-fragments-with.html?m=1 - introduces a mini framework that allows navigating by fragments.
A similar approach is used in the Shakespeare sample in the mvvmcross-tutorials fragments sample.
It should be possible to adapt that code to your needs