インターフェースとDataBindingを使ってログインする

##はじめに
前回作成した [【Android】DataBindingを使ってログインする](https://qiita.com/kilalabu/items/20ca88ca0f11f1199c66)のコードを振り返ってみて、
handlersではなくインターフェースを使った実装もあるとわかったので記録しておく。

##やりたいこと
* handlersを使わずに実装したい
* OnClickListenerは使いたくない（ActivityではなるべくViewを表示させる処理だけに留めたいため）<br>
**→ボタンクリック後の処理(画面遷移させる処理)をViewModelで実行したい**

```java:該当箇所(抜粋)
public class LoginActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    // (省略)
        // xmlのhandlersにLoginActivityのonLoginClick()を紐付ける
        binding.setHandlers(this);
    }

    // buttonをクリックしたときのイベント処理
    @Override
    public void onLoginClick(View view) {
        if (viewModel.isValidInputValue()) {
            Intent intent = new Intent(LoginActivity.this, MainActivity.class);
            startActivity(intent);
            finish();
        } else {
            binding.clickText.setText("6文字以上入力してください");
        }
    }
}
```

【悩んだこと】
ViewModel内でActivityを呼び出すのはMVVMに反してしまう。
どうやってViewModelで画面遷移させる処理を呼び出せば良いんだろう？

【導き出したこと】
インターフェースを挟み、あえて曖昧にしたviewを使う。

## 抜粋コード
### インターフェースを定義
```java:LoginView.java
public interface LoginView {
    void moveToMainView();
}
```

### ViewModelで遷移する処理を実行
インターフェースを介してViewModel内で擬似的にviewをセットする

```java
public class UserViewModel extends BaseObservable {

  private LoginView view

  // (省略)

  // Activityのviewを紐付けさせるために使う
  public void setView(LoginView view) {
    this.view = view
  }

  public void validateLogin() {
    if (バリデーション) {
      view.moveToMainView();
    } else {
      // エラー処理  
    }
  }
}
```


### インターフェースを実装
```java:LoginActivity.java
public class LoginActivity extends AppCompatActivity implements LoginView {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // (省略)

        UserViewModel viewModel = new UserViewModel();
        // ここでViewModelにActivityのView(this)を渡す
        viewModel.setView(this);
    }

    // LoginViewのメソッドをオーバーライド
    @Override
    public void moveToMainView() {
        Intent intent = new Intent(LoginActivity.this, MainActivity.class);
        startActivity(intent);
        finish();
    }
}
```

### ボタンクリック後に呼び出す処理を指定する
レイアウト内にて指定

```xml
<Button
  android:onClick="@{(v) -> viewModel.validateLogin()}" />        
```


## 全文コード

### Interface
```java:LoginView.java
public interface LoginView {
    void moveToMainView();
}
```
### ViewModel
```java:UserViewModel.java
public class UserViewModel extends BaseObservable {
    private String email;
    private String password;
    private String clickText = "";
    private LoginView view;

    @Bindable public String getEmail() {
        return email;
    }

    @Bindable public String getPassword() { return password; }
    
    @Bindable public String getClickText() {
        return clickText;
    }

    public void setEmail(String email) {
        this.email = email;
        notifyPropertyChanged(BR.buttonEnable);
    }

    public void setPassword(String password) {
        this.password = password;
        notifyPropertyChanged(BR.buttonEnable);
    }

    public void setView(LoginView view) {
        this.view = view;
    }
    
    @Bindable public boolean isButtonEnable() {
        return !TextUtils.isEmpty(email) && !TextUtils.isEmpty(password);
    }

    public void validateLogin() {
        if (email.length() >= 6 && password.length() >= 6) {
            view.moveToMainView();
        } else {
            clickText = "6文字以上入力してください";
            notifyPropertyChanged(BR.clickText);
        }
    }
}
```
### Activity
```java:LoginActivity.java
public class LoginActivity extends AppCompatActivity implements LoginView {

    ActivityLoginBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        binding = DataBindingUtil.setContentView(this, R.layout.activity_login);

        UserViewModel viewModel = new UserViewModel();
        // ここでViewModelにActivityのView(this)を渡す
        viewModel.setView(this);
        binding.setViewModel(viewModel);
    }

    @Override
    public void moveToMainView() {
        Intent intent = new Intent(LoginActivity.this, MainActivity.class);
        startActivity(intent);
        finish();
    }
}
```
### レイアウト

```xml:activity_login.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="viewModel"
            type="com.example.loginapp.UserViewModel" />
    </data>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/clickText"
            android:layout_width="320dp"
            android:layout_height="wrap_content"
            android:text="@{viewModel.clickText}" />


        <EditText
            android:id="@+id/mail_form"
            android:layout_width="320dp"
            android:layout_height="wrap_content"
            android:imeOptions="actionDone"
            android:inputType="textEmailAddress"
            android:text="@={viewModel.email}"
         />

        <EditText
            android:id="@+id/pass_form"
            android:layout_width="320dp"
            android:layout_height="wrap_content"
            android:imeOptions="actionDone"
            android:inputType="textPassword"
            android:text="@={viewModel.password}"
           />

        <Button
            android:id="@+id/login"
            android:layout_width="320dp"
            android:layout_height="wrap_content"
            android:text="ログイン"
            android:enabled="@{viewModel.buttonEnable}"
            android:onClick="@{(v) -> viewModel.validateLogin()}" />

    </LinearLayout>
</layout>
```

## 最後に
「ViewModelからLiveData、RxにてActivityへ通知」までが完成形っぽいので早くできるようになりたい。