【问题标题】:How to set click listener and pass edittext fields value to view model using data binding如何使用数据绑定设置单击侦听器并将edittext字段值传递给视图模型
【发布时间】:2017-10-21 10:08:56
【问题描述】:

您好,我正在尝试在我的 android 应用程序中使用数据绑定和 mvvm 架构。我想在布局中使用数据绑定添加点击监听器,并将用户名和密码edittext 的值发送到视图模型,它将执行 Web 服务并调用 LoginActivity 的适当方法,如 startHomeActivity()

有谁知道该怎么做还是我采取了错误的方法?我的活动、布局和视图模型的代码如下 sn-p

LoginActivity.kt

class LoginActivity : BaseActivity(), LoginNavigator {

    @Inject
    lateinit var loginViewModel: LoginActivityViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val activityLoginBinding = DataBindingUtil.setContentView<ActivityLoginBinding>(this, R.layout.activity_login)


    }

    override fun startHomeActivity() {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun startRegistrationActivity() {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun startForgotPasswordActivity() {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun handleError(throwable: Throwable) {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

}

LoginActivityViewModel.kt

class LoginActivityViewModel {


    fun login(email: String, password: String) {

    }

    /**
     * Validate email and password. It checks email and password is empty or not
     * and validate email address is correct or not
     * @param email email address for login
     * @param password password for login
     * @return true if email and password pass all conditions else false
     */
    fun isEmailAndPasswordValid(email: String, password: String): Boolean {

        if (email.isEmpty()) return false

        if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) return false

        if (password.isEmpty()) return false

        return true
    }

}

activity_login.xml

<layout>

    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true"
        tools:context="com.app.android.login.LoginActivity"
        tools:ignore="missingPrefix">

        <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingBottom="@dimen/default_view_margin_bottom_8dp">

            <android.support.design.widget.TextInputLayout
                android:id="@+id/til_login_email"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
                android:layout_marginStart="@dimen/default_view_margin_left_8dp"
                android:textColorHint="@color/colorSecondaryText"
                app:hintTextAppearance="@style/AppTheme.InputLayoutStyle"
                app:layout_constraintBottom_toTopOf="@+id/til_login_password"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintVertical_chainStyle="packed">

                <android.support.design.widget.TextInputEditText
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/login_email"
                    android:imeOptions="actionNext"
                    android:singleLine="true"
                    android:textColor="@color/colorPrimaryText" />
            </android.support.design.widget.TextInputLayout>

            <android.support.design.widget.TextInputLayout
                android:id="@+id/til_login_password"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
                android:layout_marginStart="@dimen/default_view_margin_left_8dp"
                android:textColorHint="@color/colorSecondaryText"
                app:hintTextAppearance="@style/AppTheme.InputLayoutStyle"
                app:layout_constraintBottom_toTopOf="@+id/btn_login_login"
                app:layout_constraintTop_toBottomOf="@+id/til_login_email"
                app:layout_constraintVertical_chainStyle="packed">

                <android.support.design.widget.TextInputEditText
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/login_password"
                    android:imeOptions="actionDone"
                    android:singleLine="true"
                    android:textColor="@color/colorPrimaryText" />
            </android.support.design.widget.TextInputLayout>

            <Button
                android:id="@+id/btn_login_login"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
                android:layout_marginStart="@dimen/default_view_margin_left_8dp"
                android:layout_marginTop="48dp"
                android:text="@string/login_btn_text"
                android:textColor="@color/colorWhite"
                app:layout_constraintBottom_toTopOf="@+id/textview_login_forgot_password"
                app:layout_constraintTop_toBottomOf="@+id/til_login_password"
                app:layout_constraintVertical_chainStyle="packed" />

            <TextView
                android:id="@+id/textview_login_forgot_password"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
                android:layout_marginStart="@dimen/default_view_margin_left_8dp"
                android:layout_marginTop="36dp"
                android:gravity="center"
                android:text="@string/login_forgot_password"
                app:layout_constraintBottom_toTopOf="@+id/btn_login_register"
                app:layout_constraintTop_toBottomOf="@+id/btn_login_login"
                app:layout_constraintVertical_chainStyle="packed" />

            <Button
                android:id="@+id/btn_login_register"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
                android:layout_marginStart="@dimen/default_view_margin_left_8dp"
                android:text="@string/login_sign_up"
                android:textColor="@color/colorWhite"
                app:layout_constraintBottom_toBottomOf="parent" />

        </android.support.constraint.ConstraintLayout>
    </ScrollView>
</layout>

【问题讨论】:

    标签: android mvvm kotlin android-databinding android-mvvm


    【解决方案1】:

    首先重命名您的 ViewModel。它由 View 分隔,这意味着名称应该类似于 LoginViewModel。对于这种尝试(这是在 android 中使用 mvvm 模式的最佳方法),您需要 AAC/LiveData

    其次,您应该进行双向数据绑定并将 ViewModel 分配给您的布局。

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable name="viewModel"  type="...YourVm" />
    </data>
    <android.support.design.widget.TextInputEditText   ...
                        android:text="@={viewModel.yourField}" />
    
    <Button ... onClick="@{viewModel.onClick}"    />
    </layout>
    

    这需要在您的 ViewModel 中使用 ObservableField&lt;String&gt;

    现在您想通过在您的活动中传递点击事件来验证是否发生了点击。对于这种情况,您在 ViewModel 中创建侦听器并将数据传递给 Observable。

    class LoginViewModel {
    
        val yourField = ObservableField<String>()
        val uiEventLiveData = SingleLiveData<Int>()
    
        fun onClick(view:View) {
           uiObservable.data = 1 // or any other event
        }
    }
    

    在此之后,您可以使用您的 Activity 或 Fragment 使用 LiveData 观察 UIEvents(这是生命周期感知的!)。

    现在您可以使用绑定到 ViewModel 的任何 Fragment / Activity 来观察 UI 事件,例如:

    class YourActivity {
    
    
    private val yourvm by lazy { ViewModelProviders.of(this, viewModelFactory).get(Yourvm::class.java) } 
    
     override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
      // .... 
      binding.viewModel = yourVm
    } 
    
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
    
        yourVm.uiEventLiveData.observe(this, Observer {
              when(it) {
                1->  {  doSomeLoginStuff(yourVm.yourField, ...) } //click happened, do something
                else -> .... // unknown ui event
              }
        })
    }
    

    您需要类 SingleLiveData,它是一个 MutableLiveData,但会使您的数据在其发出的数据中无效。

    class SingleLiveData<T> : MutableLiveData<T>() {
    
        private val mPending = AtomicBoolean(false)
    
        @MainThread
        override fun observe(owner: LifecycleOwner, observer: Observer<T>) {
    
            if (hasActiveObservers()) {
                Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
            }
    
            // Observe the internal MutableLiveData
            super.observe(owner, Observer { t ->
                if (mPending.compareAndSet(true, false)) {
                    observer.onChanged(t)
                }
            })
        }
    
        @MainThread
        override fun setValue(t: T?) {
            mPending.set(true)
            super.setValue(t)
        }
    
        /**
         * Used for cases where T is Void, to make calls cleaner.
         */
        @MainThread
        fun call() {
            value = null
        }
    
        companion object {
            private val TAG = "SingleLiveData"
        }
    }
    

    有几次尝试使用 Wea​​kReferences 来避免上下文泄漏,但我强烈建议不要这样做。原因是您想将逻辑与您的视图分开。即使引用是惰性或弱引用,也会破坏架构。

    【讨论】:

    • 不要将 UI 内容传递给您的 ViewModel。这不应该发生:fun onClick(view:View)。您的 UI 随时可能被销毁,让您的 ViewModel 挂在无效的视图实例上。
    猜你喜欢
    • 2018-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多