mvvmcross IOS: How to callback from a ViewModel to

2019-08-07 10:48发布

问题:

I have a MvxViewController and in the ViewDidLoad i bind the button click to the viewmodel. When the button is clicked I open another view in which I will need to return a string back to my first view

    public override void ViewDidLoad ()
    {
        var set = this.CreateBindingSet<MyView1, MyView1ViewModel>();
        set.Bind(myButton).To(vm => vm.MyButtonCommand);
        set.Apply();
    }

    public ICommand MyButtonCommand
    {
        get
        {
            _myButtonCommand = _myButtonCommand ?? new MvxCommand(MyButtonCommandClick);
            return _myButtonCommand;
        }
    }
    private void MyButtonCommandClick()
    {
        ShowViewModel<ViewModelNumber2>();
    }

After some logic is ran in my second view I want to return the string

    private void SomeMethodInViewModelNumber2()
    {
        //Raise event that will get pickup up in MyView
        //Or somehow get "SomeString"
        if (OnMyResult != null)
            OnMyResult ("SomeString");
    }

The problem is that I don't want to send the string back using the messenger. I have my reasons but basically because ViewModelNumber2 can be opened from many different places and works slightly different and managing the different messages that would need to be sent back and where to subscribe to these messages would be a mess

Is there any way that I can do something like the below?

    public override void ViewDidLoad ()
    {
        var set = this.CreateBindingSet<MyView1, MyView1ViewModel>();
        set.Bind(myButton).To(vm => vm.MyButtonCommand).OnMyResult((myString) => {Process(myString)});
        set.Apply();
    }

Or perhaps when I create ViewModelNumber2 I should pass a callBack into the constructor and use that to send the string back from ViewModelNumber2 to MyView1ViewModel

ShowViewModel<ViewModelNumber2>(OnMyResult);

What is the best way to do this?

回答1:

In short: I don't know what "the best way to do this" is.

The area of ChildViewModel-ParentViewModel messages is complicated - especially because on platforms like Android using Activities and WindowsPhone using Pages you have no guarantee that the ParentViewModel will be in memory when the Child is shown. (Note: this isn't a problem on iOS as its "app suspension" model is simpler)

When I do need one ViewModel returning data to another, then:

  • Often I try to implement the data collection views as "popup dialogs" rather than as "whole pages" - this makes the parent-child ViewModel relationship more correct - and ensures the parent ViewModel will be in memory when the child closes.

  • Often I recommend people use a Messenger-based technique like Greg describes in: http://www.gregshackles.com/2012/11/returning-results-from-view-models-in-mvvmcross/

    • often I've done this messaging via background services rather than via ViewModel-ViewModel messaging (a bit like the way screens are updated in https://github.com/MvvmCross/NPlus1DaysOfMvvmCross/tree/master/N-17-CollectABull-Part6)
  • Another solution I've used is to:

    • implement a IDropBoxService singleton - with an API like void Deposit(key, value) and bool TryCollect(key, out value)
    • allow the closing "child" ViewModels to leave "values" when they close
    • implement IVisible functionality in my "parent" ViewModel - like in https://github.com/MvvmCross/NPlus1DaysOfMvvmCross/blob/master/N-42-Lifecycles/Lifecycle.Core/ViewModels/FirstViewModel.cs#L10
    • use the IVisible method to check for messages

To implement anything perfectly, you really should add serialisation code to make sure this all works during "tombstoning" on all platforms... but often this is overkill - for a simple data collection dialog users often don't need "perfect" tombstoning support.