In MVVMCross, is it possible to close a viewmodel and pass values back to the previous viewmodel in the navigation stack?
Asked Answered
C

4

8

Consider the following example. I have three view models, ViewModel_A, ViewModel_B, and ViewModel_Values.

I want to be able to navigate to ViewModel_Values from either ViewModel_A or ViewModel_B, select a value from ViewModel_Values, then return that value to the calling view model.

Is there a way of passing arguments to previous view models in the navigation stack so that I can simply call ViewModel_Values.Close(this), thereby ensuring that the ViewModels_Values is decoupled from any other view models and can be used with arbitrary "parent" view models?

Coh answered 1/5, 2017 at 22:52 Comment(0)
C
0

Installing & using the MvxMessenger plugin is a great way to decouple view model communication in MvvmCross -

In your case, you could set up a new message -

public class ValuesChangedMessage : MvxMessage
{      
    public ValuesChangedMessage(object sender, int valuea, string valueb)
        : base(sender)
    {
        Valuea = valuea;
        Valueb = valueb;        
    }

    public int Valuea { get; private set; }
    public string Valueb { get; private set; }
}

In ViewModel_Values, you would act on / publish your UX changes with -

_mvxMessenger.Publish<ValuesChangedMessage>(new ValuesChangedMessage(this, 1, "boo!"));

And in ViewModel_A, ViewModel_B you would subscribe and act on them (as your ViewModel A / B would be still in the navigation stack when you pushed ViewModel_Values from them, so they could receive the message) -

private MvxSubscriptionToken _messageToken;              

_messageToken = _mvxMessenger.Subscribe<ValuesChangedMessage>(async message =>
        {
            // use message.Valuea etc ..
        });

More infos here -

https://www.mvvmcross.com/documentation/plugins/messenger?scroll=644 https://www.youtube.com/watch?feature=player_embedded&v=HQdvrWWzkIk

Collencollenchyma answered 2/5, 2017 at 6:21 Comment(1)
Even though this would work, this is not a very sustainable and safe way to implement what you want. MvvmCross support many other solution to do this, like @Daisey mentionsBuoyant
D
11

MvvmCross 5 onwards

From MvvmCross 5 you can use the new IMvxNavigationService that allows you to have a much richer navigation. One of the new features is the possibility to await a value from another ViewModel after navigating to it and should be the approach to take after MvvmCross 5 instead of Messenger, e.g.:

public class ViewModel_A : MvxViewModel
{
    private readonly IMvxNavigationService _navigationService;
    public ViewModel_A(IMvxNavigationService navigation)
    {
        _navigationService = navigationService;
    }

    public override async Task Initialize()
    {
        //Do heavy work and data loading here
    }

    public async Task SomeMethod()
    {
        var result = await _navigationService.Navigate<ViewModel_Values, MyObject, MyReturnObject>(new MyObject());
        //Do something with the result MyReturnObject that you get back
    }
}

public class ViewModel_Values : MvxViewModel<MyObject, MyReturnObject>
{
    private readonly IMvxNavigationService _navigationService;
    public ViewModel_Values(IMvxNavigationService navigation)
    {
        _navigationService = navigationService;
    }

    public override void Prepare(MyObject parameter)
    {
        //Do anything before navigating to the view
        //Save the parameter to a property if you want to use it later
    }

    public override async Task Initialize()
    {
        //Do heavy work and data loading here
    }

    public async Task SomeMethodToClose()
    {
        // here you returned the value
        await _navigationService.Close(this, new MyReturnObject());
    }
}

More info here HIH

Daisey answered 28/11, 2017 at 12:50 Comment(5)
Is it possible to add more calls to the stack without return null to the first caller? Right now, in ViewModel_Values, if I navigate to another viewmodel, expecting a return value, it returns null to the first caller, so I cannot add more than one level.Uraninite
There is an issue in the MvvmCross repository and there you can find the workaround, here the link github.com/MvvmCross/MvvmCross/issues/…Daisey
Thanks so much! I'm going to try it :)Uraninite
Problem here, if I go back using the default top/left navigation bar back button, the Close() is never called, and your result will never get any value. Actually, the await never returns.Cinerarium
it should return and result should be null. Which version of MvvmCross are you using and which platform?Daisey
M
4

Use messaging center. Here is the sample code.

//for trigger
MessagingCenter.Send<object> (this, "Hi");

//put this where you want to receive your data
MessagingCenter.Subscribe<object> (this, "Hi", (sender) => {
    // do something whenever the "Hi" message is sent
});
Martinmartina answered 2/5, 2017 at 1:54 Comment(0)
C
0

Installing & using the MvxMessenger plugin is a great way to decouple view model communication in MvvmCross -

In your case, you could set up a new message -

public class ValuesChangedMessage : MvxMessage
{      
    public ValuesChangedMessage(object sender, int valuea, string valueb)
        : base(sender)
    {
        Valuea = valuea;
        Valueb = valueb;        
    }

    public int Valuea { get; private set; }
    public string Valueb { get; private set; }
}

In ViewModel_Values, you would act on / publish your UX changes with -

_mvxMessenger.Publish<ValuesChangedMessage>(new ValuesChangedMessage(this, 1, "boo!"));

And in ViewModel_A, ViewModel_B you would subscribe and act on them (as your ViewModel A / B would be still in the navigation stack when you pushed ViewModel_Values from them, so they could receive the message) -

private MvxSubscriptionToken _messageToken;              

_messageToken = _mvxMessenger.Subscribe<ValuesChangedMessage>(async message =>
        {
            // use message.Valuea etc ..
        });

More infos here -

https://www.mvvmcross.com/documentation/plugins/messenger?scroll=644 https://www.youtube.com/watch?feature=player_embedded&v=HQdvrWWzkIk

Collencollenchyma answered 2/5, 2017 at 6:21 Comment(1)
Even though this would work, this is not a very sustainable and safe way to implement what you want. MvvmCross support many other solution to do this, like @Daisey mentionsBuoyant
A
0

In my case of trying to navigate in this pattern:

//pseudo code
"ModelA" => "ModelB<List<MyObject>>" => "ModelC<MyObject>" 

OR

//pseudo code
"ModelA" => "ModelC<MyObject>" 

I used the following work around in my ViewDestroy() override of ModelB<List>:

    private bool destroyView = true;
    public bool DestroyView
    {
        get => destroyView;

        set
        {
            destroyView = value;
            RaisePropertyChanged(() => DestroyView);

        }

    }

    public override void ViewDestroy(bool viewFinishing)
    {
        viewFinishing = DestroyView;
        base.ViewDestroy(viewFinishing);
    }

    private async Task ModifySelectedObject()
    {
        DestroyView = false;
        MyObject obj = SelectedObject;
        MyObject modifiedObj = await _navigationService.Navigate<ModifySingleViewModel, MyObject, MyObject>(new MyObject());

        if (modifiedObj != null)
        {
            obj = modifiedObj;
        }
        else
        {
            await Application.Current.MainPage.DisplayAlert("", "No changes made.", "OK");
        }
        DestroyView = true;
    }

This keeps the original

"await _navigationService.Navigate<ModifyMultipleViewModel, List, List>(new MyObject);"

from ModelA open when navigating to ModelC from ModelB, but still allows the ViewDestroy Method to close otherwise.

Albie answered 15/8, 2021 at 20:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.