Google SignInButton的onClick无法使用DataBinding

当我尝试在我的Google的onClick中设置SignInButton方法时:

android:onClick="@{() -> viewModel.onGoogleLoginClick()}"

我总是得到这个错误:

发现数据绑定错误。

**** /数据绑定错误**** msg:无法为android找到正确的回调类:onClick。试过android.view.View但它有0个抽象方法,应该有1个抽象方法。

file:/Users/user/Android/project/app/src/main/res/layout/activity_login.xml loc:53:31 - 53:66 **** \ data binding error ****

这是我的代码:

activity_login.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".ui.login.LoginActivity">
    <data>
        <import type="android.view.View" />
        <variable
            name="viewModel"
            type="com.example.myapp.ui.login.LoginViewModel" />
    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal"
        android:orientation="vertical"
        android:padding="@dimen/default_layout_padding">

        <EditText
            android:id="@+id/login_name_editText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/login_username_hint"
            android:inputType="text"
            android:text="@{viewModel.mEmail}" />

        <EditText
            android:id="@+id/login_pass_editText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/login_name_editText"
            android:hint="@string/login_password_hint"
            android:inputType="numberPassword"
            android:text="@{viewModel.mPassword}" />

        <Button
            android:id="@+id/login_login_button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/login_pass_editText"
            android:onClick="@{() -> viewModel.onServerLoginClick()}"
            android:text="@string/login_login_button_text"
            android:textAllCaps="true" />

        <com.google.android.gms.common.SignInButton
            android:id="@+id/login_google_button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/login_login_button"
            android:onClick="@{() -> viewModel.onGoogleLoginClick()}"/>
    </RelativeLayout>
</layout>

LoginViewModel.class

public class LoginViewModel extends BaseViewModel<LoginNavigator> implements
        GoogleApiClient.OnConnectionFailedListener, OnCompleteListener<GoogleSignInAccount>,
        GoogleApiClient.ConnectionCallbacks {

    private static final String LOG_TAG = LoginViewModel.class.getSimpleName();

    public String mEmail;
    public String mPassword;

    public LoginViewModel(DataManager dataHelper, SchedulerProvider schedulerProviderHelper) {
        super(dataHelper, schedulerProviderHelper);
    }

    public void onServerLoginClick() {
        if (CommonUtils.loginDataIsCorrect(mEmail, mPassword)) {
            doServerLogin(mEmail, mPassword);
        } else {
            getNavigator().handleError();
        }
    }

    public void onGoogleLoginClick() {
        getNavigator().googleLogin();
    }

    // Server
    private void doServerLogin(String name, String pass) {
        ...
    }

    // Google
    protected void doGoogleLogin(FragmentActivity fragmentActivity, Context context) {
        ...
    }
    ...
}

LoginActivity.class

public class LoginActivity extends BaseActivity<ActivityLoginBinding, LoginViewModel> implements LoginNavigator {

    private static final int REQUEST_CODE_REGISTER = 0;
    private static final int REQUEST_CODE_GOOGLE_SIGN_IN = 1;

    @BindString(R.string.login_data_missing_message)
    String mDataMissingMessage;

    @Inject
    LoginViewModel mLoginViewModel;
    private ActivityLoginBinding mActivityLoginBinding;

    public static Intent newIntent(Context context) {
        return new Intent(context, LoginActivity.class);
    }

    @Override
    public int getBindingVariable() {
        return BR.viewModel;
    }

    @Override
    public int getLayoutId() {
        return R.layout.activity_login;
    }

    @Override
    public LoginViewModel getViewModel() {
        return mLoginViewModel;
    }

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

        mLoginViewModel.setNavigator(this);
        mActivityLoginBinding.loginGoogleButton.setSize(SignInButton.SIZE_WIDE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case REQUEST_CODE_GOOGLE_SIGN_IN:
                mLoginViewModel.handleGoogleSignInResult(data);
                break;
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    @Override
    public void googleLogin() {
        mLoginViewModel.doGoogleLogin(this, this);
    }

    @Override
    public void showGoogleForm(GoogleApiClient googleApiClient) {
        Intent googleIntent = Auth.GoogleSignInApi.getSignInIntent(googleApiClient);
        startActivityForResult(googleIntent, REQUEST_CODE_GOOGLE_SIGN_IN);
    }
    ...
}

和BaseActivity.class,我绑定每个Activity的视图和数据:

public abstract class BaseActivity<T extends ViewDataBinding, V extends BaseViewModel> extends AppCompatActivity {

    private T mViewDataBinding;
    private V mViewModel;

    public abstract int getBindingVariable();

    @LayoutRes
    public abstract int getLayoutId();

    public T getViewDataBinding() {
        return mViewDataBinding;
    }

    public abstract V getViewModel();

    public void performDependencyInjection() {
        AndroidInjection.inject(this);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        performDependencyInjection();
        super.onCreate(savedInstanceState);
        performDataBinding();
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    private void performDataBinding() {
        mViewDataBinding = DataBindingUtil.setContentView(this, getLayoutId());
        this.mViewModel = mViewModel == null ? getViewModel() : mViewModel;
        mViewDataBinding.setVariable(getBindingVariable(), mViewModel);
        mViewDataBinding.executePendingBindings();
    }
}

有谁知道为什么这个错误?因为SignInButton实施OnClickListener。我尝试过Invalidate Caches / Restart并删除.gradle.idea文件夹,但仍然无法正常工作。

2
投票

这是一个有趣的问题,因为SignInButton扩展了View,但是doc明确指出在类中使用setOnClickListener(OnClickListener)注册一个监听器,而不是在xml中。数据绑定将lamda表达式包装为一个监听器(你可以在自动生成的数据绑定类中看到它),并且它可能不会坚持使用SignInButton所期望的监听器。例如。如果您尝试通过xml传递View.OnClickListener变量,则不应该得到编译错误,但您可能也无法接收您的点击事件(如文档中所述)。

1
投票

幸运的是,我们有@BindingAdapter解决类似的问题。这是Kotlin的一个例子:

BindingAdapters.kt

@BindingAdapter("android:onClick")
fun bindSignInClick(button: SignInButton, method: () -> Unit) {
    button.setOnClickListener { method.invoke() }
}

layout.xml

<com.google.android.gms.common.SignInButton
            ...
            android:onClick="@{() -> viewModel.onSignInClick()}" />