MVVMCross: How to bind Xamarin.Android events to ViewModel commands
Asked Answered
V

2

7

I am trying to go from an activity to another. I am still learning about MVVMCross so this whole pattern is still very new to me. I am applying it with Xamarin.Android only at the moment.

The setup:

  1. MainDashboardActivity has an Android Design Support library's NavigationView.

  2. The ViewModel MainDashboardViewModel has an IMvxCommand GoToSecondDashboard which is just a simple ShowViewModel to another activity.

The NavigationView has a NavigationItemSelected event. Normally, I would just do this:

navigationView.NavigationItemSelected += (o, e) =>
{
    if(e.MenuItem.ItemId == Resource.Id.SecondDashboardMenu)
    {
        // make new intent to target activity
    }
};

Now I have tucked the navigation logic into the ViewModel's IMvxCommand, and I want to bind it to the NavigationView's event, no longer creating intents and whatnot. How would I achieve this?

I want to use the fluent binding logic in the code file and not in the layout, like how this answer does:

protected override void OnViewModelSet()
{
    SetContentView(Resource.Layout.View_Tip);

    var edit = this.FindViewById<EditText>(Resource.Id.FluentEdit);

    var set = this.CreateBindingSet<TipView, TipViewModel>();
    set.Bind(edit).To(vm => vm.SubTotal);
    set.Apply();

    // for non-default properties use 'For':
    // set.Bind(edit).For(ed => ed.Text).To(vm => vm.SubTotal);

    // you can also use:
    //   .WithConversion("converter", "optional parameter")
    //   .OneTime(), .OneWay() or .TwoWay()
}

But NavigationItemSelected is an event. I have not been able to find a way to bind events to commands. There is also the logic of filtering ItemId before that can happen, so it's not going to even be a straightforward event-to-command binding.

I am not sure if this is the correct approach to this. All I want is to bind menu taps to commands in the code file instead of the layout file.

Vertumnus answered 17/6, 2016 at 8:21 Comment(3)
Just to be sure, you dont want to do the bindings in the layout file? The normal approach in my opinion would be: 1. Define a model for you menu items. 2. Define a list in your viewmodel for your menu items. 3. Define a layout for your menu items. 4. Bind the menu items to the NavigationView over the Layout. 5. Set the layout for the menu items to your navigationview. 6. Define a command where you expect a type of your menu items. 7. Bind the command to the NavigationView. Though if you dont want to do it over the layout, i will see if i find a part where i did it in my code.Fresco
I am used to doing binding from within the code itself, as my (limited) experience comes from dealing with Android directly, and they tend to bind from code. And muddling the layout with code feels a little alien to me, so there's that. Little of what I know of NavigationView is that it comes with a layout for the header and a menu .xml for the menu items (not layout), so how exactly do we bind to them? Is there any sample for such a process?Vertumnus
I will see if i can post a example for that later or ask my coworker who already did implement it. And though it might feel a little bit alien, it is really comfortable if you get used to it. Iam only using code bindings in special situations now. Though both have positive and negative aspects.Fresco
F
6

Since there are no Binding Targets defined for NavigationView, you won't be able to bind as Cyriac describes in his post.

What a target binding does internally is simply subscribe to an event and react to it and exposing that data as a property.

So since there is no way to take an ItemsSource and bind to a NavigationView currently, you have to do something like you are doing already, hooking an EventHandler up to the event, and call directly into your ViewModel, i.e. invoking a Command. This looks something like this:

navigationView.NavigationItemSelected += ItemSelected;

private void ItemSelected(object sender, NavigationItemSelectedEventArgs args)
{
    ViewModel.NavigateCommand.Execute(args.MenuItem.TitleFormatted.ToString());
}

Then in your ViewModel in your Command:

private void DoNavigateCommand(string title)
{
    if (title == "Derp")
        ShowViewModel<DerpViewModel>();
}

Alternatively you could wrap this code in a Target Binding. You can see how these are implemented in the official MvvmCross github repository.

Forbiddance answered 17/6, 2016 at 12:46 Comment(4)
Oh, kinda imagined the bindings were added for it. Good to know! Ty!Fresco
Awesome! Sounded like what I wanted. I will verify on Monday!Vertumnus
Okay, it's working, but a little different than your code. I had to cast the ViewModel with (ViewModel as MainDashboardViewModel).NavigateToViewModel("second").Execute(); and in the ViewModel,NavigateToViewModel is a simple function that returns a command for the activity to execute. How do you setup the ViewModel so that the activity can execute the command directly like you do?Vertumnus
(oops I was mistaken, the ViewModel didn't even need to be casted.)Vertumnus
F
3

I found an answer by someone else on http://crosscuttingconcerns.com/MvvmCross-Fluent-Databinding , which you should try out. I think you just cant reference directly the Event, rather have to use the string.

protected override void OnViewModelSet ()
{
        SetContentView (Resource.Layout.TermsPage);

        var set = this.CreateBindingSet<TermsView, TermsViewModel>();
        set.Bind(FindViewById<Button>(Resource.Id.acceptTermsButton))
            .For("Click")
            .To(vm => vm.AcceptTermsCommand);
        set.Apply();
}

Well of course you have do adjust it depending on your event.

Fresco answered 17/6, 2016 at 10:50 Comment(1)
I have left work so I cannot test this for now... will update on Monday!Vertumnus

© 2022 - 2024 — McMap. All rights reserved.