Blog Post

This is part 2 of a 4 part series: part 1, part 3, part 4

In the previous post we got an Android application up and running with the Data Binding Library with some simple one-way binding.

In this post, we’re continuing the implementation of LoginActivity, attempting to build the view and view model using the MVVM pattern.

Binding the Button

Let’s revisit how we would bind a button click to a view model method with binding in Xamarin Forms :

<Button Text="{i18n:Translate LoginButton}" Command="{Binding DoLoginCommand}" />
public class LoginViewModel : BaseViewModel
{
  Command _doLoginCommand;
  public Command DoLoginCommand
  {
    get { return _doLoginCommand; }
  }

  public LoginViewModel()
  {
    _doLoginCommand = new Command(x=> Login());
  }

  async Task Login()
  {
    // do stuff
  }
}

Let’s consider another example, using HTML and AngularJS:

<button ng-click="login.onSubmit()">Sign in</button>
angular.module('app.login')
  .controller('LoginController',
    function() {
      this.onSubmit = function() {
        // do something
      };
    });

Step 2 - login OnClickListener

In Android, button clicks are typically handled with an OnClickListener.

My first attempt to bind the button click uses this pattern:

<Button android:text="@string/label_login"
  android:setOnClickListener="@{viewModel.getLoginCommand}"/>

And the view model implementation:

public class LoginViewModel extends BaseObservable {

  private String username;
  private String password;

  private Button.OnClickListener loginCommand;

  public LoginViewModel(){
    loginCommand = new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        Log.d("db", String.format("username=%s;password=%s", username, password));
      }
    };
  }

  @Bindable
  public String getUsername() { return this.username; }

  @Bindable
  public String getPassword() {
    return this.password;
  }

  public Button.OnClickListener getLoginCommand() { return loginCommand; }

  public void setUsername(String username) {
    this.username = username;
    notifyPropertyChanged(com.petermajor.databinding.BR.username);
  }

  public void setPassword(String password) {
    this.password = password;
    notifyPropertyChanged(com.petermajor.databinding.BR.password);
  }
}

The good news: it works! If you start the application and click the button, you will see the following entry in logcat:

D/db﹕ [email protected];password=Passw0rd

The bad news: the code is not really in the spirit of MVVM. The view model references Button.OnClickListener - a view specific class that really shouldn’t in the view model.

The implementation of the onClick method has a View parameter… how would you write a unit test that passed in a View to the view parameter?

Step 3 - create a Command class to execute button clicks

An easy way to clean this up is to create a Command class.

This could secretly expose the OnClickListener interface (among others potentially). However, the view model does not need to know about all this View-i-ness.

package com.petermajor.databinding;

import android.view.View;

public abstract class Command implements View.OnClickListener {

  @Override
  public void onClick(View v) {
    onExecute();
  }

  public abstract void onExecute();
}
package com.petermajor.databinding;

import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.util.Log;

public class LoginViewModel extends BaseObservable {

  private String username;
  private String password;

  private Command loginCommand;

  public LoginViewModel(){
    loginCommand = new Command() {
      @Override
      public void onExecute() {
        Log.d("db", String.format("username=%s;password=%s", username, password));
      }
    };
  }

  @Bindable
  public String getUsername() {
    return this.username;
  }

  @Bindable
  public String getPassword() {
    return this.password;
  }

  public Command getLoginCommand() { return loginCommand; }

  public void setUsername(String username) {
    this.username = username;
    notifyPropertyChanged(com.petermajor.databinding.BR.username);
  }

  public void setPassword(String password) {
    this.password = password;
    notifyPropertyChanged(com.petermajor.databinding.BR.password);
  }
}

That’s better. The view model doesn’t know how the caller will execute the command. The view model exposes the command and the details of how Command.onExecute() gets called is hidden away.

Binding directly to a method

New to the Data Binding Library in RC1 (instead of RC0), the Android Data Binding Guide states that you can bind an onClick handler directly to a method on the view model, like so:

<Button android:text="@string/label_login"
  android:onClick="@{viewModel.onLogin}"/>

And the view model implementation:

public class LoginViewModel extends BaseObservable {

    private String username;
    private String password;

    @Bindable
    public String getUsername() {
        return this.username;
    }

    @Bindable
    public String getPassword() {
        return this.password;
    }

    public void onLogin(View view) {
        Log.d("db", String.format("username=%s;password=%s", username, password));
    }

    public void setUsername(String username) {
        this.username = username;
        notifyPropertyChanged(com.petermajor.databinding.BR.username);
    }

    public void setPassword(String password) {
        this.password = password;
        notifyPropertyChanged(com.petermajor.databinding.BR.password);
    }
}

However, when compiling I kept getting this error:

Execution failed for task ‘:app:compileDebugJavaWithJavac’. java.lang.RuntimeException: failure, see logs for details. cannot generate view binders java.lang.NullPointerException at android.databinding.tool.expr.FieldAccessExpr.hasBindableAnnotations(FieldAccessExpr.java:107)

While the simplicity of this approach is very appealing, it still suffers from the problem that the method gets passed an instance of View, so I have mixed feelings about whether to use this approach or not (if it compiled for me, which it currently does not).

I’ve committed the code to branch Step3-bind-to-method

EDIT: this appears to be this issue

Next steps

In this post, we’ve bound the button click event to a command on the view model. But there’s a problem with the view model as it’s implemented up to Step 3…

Try running the application, can you find the issue? All will be revealed in the next post…

The code samples for this post can be found in github in branches “Step2” and “Step3”