Navigate and then send data back with Xamarin.Forms and MVVM Light

…By Jesse Liberty & Eric Grover

The Challenge : Fetch

Navigate to a second page, present an Editor (multi-linefetch entry) and then send the data back to the original page (and navigate back at the same time).

Behaviors, Commands and Messages, Oh my!

Behaviors will let us turn events into commands, commands will let us handle events in the view model, and messages will let us communicate from a ViewModel to another ViewModel or to a page.

We’ll combine all this with the power of MVVM Light.

Starting on the first page

We’re going to have two pages: firstPage and the notesPage.     NotesPage will have just two controls: an Editor and a button.  FirstPage will have a button to set things going.

Here’s the button on FirstPage:

    <Button Text="Notes" Command="{Binding NotesCommand}"
            TextColor="{StaticResource TextForegroundColor}"/>

Notice that the button refers to a command.  In the first page’s view model we need to handle that command.  To do so we’ll declare a backing variable and a RelayCommand property:

private RelayCommand notesCommand;

public RelayCommand NotesCommand => notesCommand ??
                     (notesCommand = new RelayCommand( GetNotes ));

That RelayCommand calls the GetNotes method,

public void GetNotes()
{
  var navigateTo = typeof(NotesPage);
  var message = new NavigationMessage { NavigateTo = navigateTo };
  Messenger.Default.Send( message );

Here I’m sending a message to the page that I want to navigate to the NotesPage.  For this to work, I’ve registered this message in my BaseViewPage (from which all my other views derive).

       Messenger.Default.Register<NavigationMessage>(
         this,
         HandleNavigation);
 

 

Here’s the implementation for HandleNavigation…

public async void 
   HandleNavigation(NavigationMessage navigationMessage)
  {
    var target = 
        Activator.CreateInstance(navigationMessage.NavigateTo);
 
    await Navigation.PushAsync((ContentPage) target);
 
  }

Once we get to NotesPage we need a way to gather the info and send it back to FirstPage. Here’s the XAML:

<StackLayout>
 
    <Editor Text="{Binding Note, Mode=TwoWay}" 
              BackgroundColor="Aqua">
 
      <Editor.Behaviors>
        <behaviors:CompletedCommandBehavior 
                Command="{Binding EditorCompleted}"/>
      </Editor.Behaviors>
    </Editor>
    
    <Button Text="Save"/>
 
  </StackLayout>

As you can see, this code depends on a CompletedCommandBehavior. One of the principal jobs of a behavior is to help turn an event into a command.  Check out this excellent article by David Britch

Before we dive into behaviors, let’s look at what is happening in  NotesViewModel. We start by creating a private  property Note

 
 
  private string note;
  public string Note
  {
    get { return note; }
    set { Set( ( ) => Note, ref note, value ); }
  }

We then create a public RelayCommand property

private RelayCommand editorCompleted;
public RelayCommand EditorCompleted => editorCompleted ??
     (editorCompleted = new RelayCommand( NotifyEditorCompleted ));

Notice that the method it is tied to is NotifyEditorCompleted.  Here is that method,

private void NotifyEditorCompleted()
{
 
  var navBack = new NavigationBackMessageWithText();
  navBack.MessageText = Note;
  Messenger.Default.Send(navBack);
}

So, when the editor fires its completed event we instantiate a NavigationBackMessageWithText, set its MessageText to the contents of Note, and then use MVVM Light’s Messenger to send that message.

Key is that we must register for that message in the calling page’s view model (!).   This is classic publish/subscribe.  Our base page subscribes to this message for the pages, and PageOneViewModel subscribes to it as well.

Messenger.Default.Register<NavigationBackMessageWithText>
  (
    this,
    HandleNavigationBack
  );

Here’s the implementation of HandleNavigationBack,

public void HandleNavigationBack(
           NavigationBackMessageWithText message)
  {
    note = message.MessageText;
  }

Creating the Behavior

Let’s return to the behavior, and take apart the one we created for this task.  First, we create the BindableProperty, which we name CommandProperty

public static readonly BindableProperty CommandProperty =
         BindableProperty.Create( 
"Command", typeof( ICommand ), 
typeof( CompletedCommandBehavior ), null );

We then created a property for the Editor itself which we’ll call AssociatedObject.  This will be needed to set the binding context of our behavior to the binding context of the page it is used in,

public Editor AssociatedObject { get; set; }

Now we create our Command property.  Notice that it is of type ICommand,

public ICommand Command
{
  get { return (ICommand) GetValue(CommandProperty); }
  set {  SetValue(CommandProperty, value);}
}

We have three jobs left in creating this behavior,

  1. Handle attaching and detaching the Completed event
  2. Handle the work we want to do when the Editor fires its Completed event
  3. Handle the Binding Context

We attach and detach the Completed event by overriding the OnAttachedTo and the OnDetachingFrom methods,

protected override void OnAttachedTo( Editor bindable )
   {
     base.OnAttachedTo(bindable);
     AssociatedObject = bindable;
      bindable.Completed += HandleEditorCompleted;
      bindable.BindingContextChanged += OnBindingContextChanged;
   }

 
   protected override void OnDetachingFrom( Editor bindable )
   {
     base.OnDetachingFrom(bindable);
     bindable.Completed -= HandleEditorCompleted;
     AssociatedObject = null;
 
   }

Notice that we also register for the BindingContextChanged event.  We’ll manage that in just a moment.   Here is the code for executing our command when the EditorCompleted event is raised,

private void HandleEditorCompleted(object o, EventArgs e)
{
  this.Command.Execute(null);
}

That was easy.  Now, here’s the code for managing the BindingContextChanged event which passes through to the overridden OnBindingContextChanged method,

void OnBindingContextChanged( object sender, EventArgs e )
  {
    OnBindingContextChanged( );
  }
 
  protected override void OnBindingContextChanged( )
  {
    base.OnBindingContextChanged( );
    BindingContext = AssociatedObject.BindingContext;
  }

Notice that we’ve tied the BindingContext of the Behavior to the binding context of the Editor control.

Note, the button in the Notes page is just a way to cause the focus to move away from the text entry field.  It isn’t wired up to a command.
That’s it! The only tricky parts are creating the message handling and creating the behavior and both of these are well documented on the web.

Share

About Jesse Liberty

Jesse Liberty is an independent consultant and programmer with three decades of experience writing and delivering software projects. He is the author of 2 dozen books and multiple Pluralsight courses, and has been a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Xamarin Certified Mobile Developer and a Xamarin MVP, Microsoft MVP and Telerik MVP.
This entry was posted in Xamarin, XAML. Bookmark the permalink.

Leave a Reply

Your email address will not be published.