MVVM模式通知View加载状态

9
我现在正在使用Google的LiveData,并推荐使用MVVM模式设计。对于我的一些请求,我使用RxJava2,在SubscribeWith(...)中侦听响应。
例如,当我按下按钮将一些数据发送到远程数据源时,我会显示一些加载动画,并希望在onComplete()事件(在subscribeWith(...)内部)中隐藏它。问题是我无法从ModelView访问View。有什么办法可以让View知道应该隐藏加载动画吗? 我的当前想法是在ViewModel中创建一个接口,并在View中实现它。但这破坏了View和ViewModel分离的概念。
2个回答

26

好的,您可以使用liveData来实现这个功能 :D

在ViewModel类中,您可以像这样创建一个LiveData对象:

 MutableLiveData<Boolean> isLoading = new MutableLiveData<>();

例如,创建一个名为downloadFinished的函数并在远程代码的onComplete中调用它。

 private void downloadFinished() {
        isLoading.setValue(true);
    }

在使用视图模型的活动中,您可以观察加载的值并隐藏进度条或其他任何内容。

   TestViewModel viewModel = ViewModelProviders.of(this).get(TestViewModel.class);
        viewModel.isLoading.observe(this, new Observer<Boolean>() {
            @Override
            public void onChanged(@Nullable Boolean isLoading) {
                if (isLoading != null) {
                    if (isLoading) {
                        // hide your progress bar
                    }
                }
            }
        });

1
你应该使用postValue而不是setValue与MutableLiveData一起使用,否则你会得到“不能在后台线程上调用setValue”异常 :) - Neone

2

您也可以使用数据绑定来实现这一点,制作一个单独的布局以便在各处重复使用,laoding_state_xml。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <View
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ProgressBar
        android:id="@+id/progressBar2"
        style="?android:attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> 

然后将其包含在您所需的布局中。
<?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"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <import type="android.view.View" />

        <variable
            name="viewModel"
            type="com.blogspot.soyamr.notforgotagain.view.signin.SignInViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".view.signin.SignInFragment">

        <include
            android:id="@+id/include"
            layout="@layout/toolbar_application"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/signInButtonView"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="68dp"
            android:layout_marginEnd="16dp"
            android:onClick="@{() -> viewModel.logIn()}"
            android:text="@string/sign_in"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/passwordTextInputLayout" />

        <TextView
            android:id="@+id/noAccountTextview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="24dp"
            android:layout_marginEnd="4dp"
            android:text="@string/no_account"
            android:textColor="@android:color/black"
            app:layout_constraintEnd_toStartOf="@+id/createAccountTextView"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintHorizontal_chainStyle="packed"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/signInButtonView" />

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/emailTextInputLayout"
            style="@style/myTextInputLayoutStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="80dp"
            android:layout_marginEnd="16dp"
            app:errorEnabled="true"
            app:errorText="@{viewModel.emailErrorMessage}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/include">

            <com.google.android.material.textfield.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/email"
                android:inputType="textEmailAddress"
                android:text="@={viewModel.emailText}" />
        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/passwordTextInputLayout"
            style="@style/myTextInputLayoutStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="24dp"
            android:layout_marginEnd="16dp"
            app:errorText="@{viewModel.passwordErrorMessage}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/emailTextInputLayout">

            <com.google.android.material.textfield.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/password"
                android:inputType="textPassword"
                android:text="@={viewModel.passwordText}" />

        </com.google.android.material.textfield.TextInputLayout>

        <TextView
            android:id="@+id/createAccountTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="?attr/selectableItemBackground"
            android:clickable="true"
            android:text="@string/create_one"
            android:textColor="@color/textBlue"
            app:layout_constraintBottom_toBottomOf="@+id/noAccountTextview"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toEndOf="@+id/noAccountTextview"
            app:layout_constraintTop_toTopOf="@+id/noAccountTextview" />
<!-- **here is the important include**-->
        <include
            android:id="@+id/here_must_be_id_or_no_databinding"
            android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}"
            layout="@layout/loading_state" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

我为了更好的阐述,已经包含了整个XML。然后在你的视图模型中添加以下内容:
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider

// Override ViewModelProvider.NewInstanceFactory to create the ViewModel (VM).
class SignInViewModelFactory(private val repository: NoteRepository) :
    ViewModelProvider.NewInstanceFactory() {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T = SignInViewModel(repository) as T
}


class SignInViewModel(val repository: NoteRepository) : ViewModel() {


    private val _isLoading = MutableLiveData(true)

    val emailText = MutableLiveData("")
    val passwordText = MutableLiveData("")


    val isLoading: LiveData<Boolean> = _isLoading


    fun logIn() {
         //start loading, this will make the view start loading directly
        _isLoading.value = true
        if (isValidInput()) {
            val res = repository.logIn(LoginUser(emailText.value!!, passwordText.value!!))
        }//remove loading view
        _isLoading.value = false
    }

//code ..
}

请注意,您正在观察XML内部的isLoading变量,因此每当其值发生更改时,视图将观察到更改并开始对其进行操作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接