是否有任何解决方案可以在通过 ViewModel 中的方法参数更改时观察 View 中的 MutubleLiveData 状态

Is there any solution to observe a MutubleLiveData's state in View while it is changed through method's perameter in ViewModel

当我通过 ViewModel 中的方法传递其 (MutableLiveData) 实例的引用时,在 Fragment 中未观察到 MutableLiveData 的状态。仅发生 http 调用且值显示在日志中,未观察到对应 LiveData 状态的任何操作

我想调用一个 Http 方法,该方法的逻辑是在 LoginViewModel 中编写的,它是 BaseViewModel 的子 class。在 BaseViewModel 中我创建了一些以 MutableLiveData 作为参数的常用方法,在 LoginViewModel 的方法中调用这些方法并在 Fragment

中观察那些 LiveData

UiState.kt

sealed class UiState<T> {
    data class Progress<T>(val isLoading: Boolean) : UiState<T>()
    data class Success<T>(val successInfo: T) : UiState<T>()
    data class Failure<T>(val throwable: Throwable) : UiState<T>()
    data class Alert<T>(val alert: String) : UiState<T>()

    companion object {
        fun <T> loading(isLoading: Boolean): UiState<T> = Progress(isLoading)
        fun <T> success(successInfo: T): UiState<T>? = Success(successInfo)
        fun <T> failure(throwable: Throwable): UiState<T> = Failure(throwable)
        fun <T> alert(alert: String): UiState<T> = Alert(alert)
    }
}

Event.kt

open class Event<out T>(private val content: T) {

    private var hasBeenHandled = false

    fun getContentIfNotHandled() = if (hasBeenHandled) {
        null
    } else {
        hasBeenHandled = true
        content
    }

    fun peekContent() = content
}

BaseViewModel.kt

fun <T> onSuccessHttpResponse(state: MutableLiveData<Event<UiState<T>>>) = Consumer<Response<T>> {
    state.value = Event(loading(true))

    if (it.isSuccessful) {
        state.value = Event(loading(false))
        state.value = Event(success(it.body()!!)!!)
    } else {
        val error = Gson().fromJson(it.errorBody()?.charStream(), ApiError::class.java)

        when (it.code()) {
            Constants.ACCESS_TOKEN_REFRESH_STATUS_CODE -> state.value = Event(alert("Renew Access Token please"))
            Constants.CUSTOM_STATUS_CODE -> state.value = Event(alert(error.message!!))
            else -> state.value = Event(alert("Something went wrong"))
        }

        state.value = Event(loading(false))
    }
}

fun <T> onErrorHttpResponse(state: MutableLiveData<Event<UiState<T>>>) = Consumer<Throwable> {
    state.value = Event(loading(false))
    state.value = Event(UiState.failure(it))
}

fun <T> inputNotFoundError(state: MutableLiveData<Event<UiState<T>>>) {
        state.value = Event(loading(false))
        state.value = Event(alert("Please Filled all Info"))
    }

LoginViewModel.kt

val tutorLoginState: MutableLiveData<Event<UiState<TutorLoginResponse>>> = MutableLiveData()

fun tutorLogin(loginInfo: LoginInfo) {
    if (loginInfo.isAssigned()) {
        callLoginTutorApi(loginInfo)
    } else {
        inputNotFoundError(tutorLoginState)
    }
}

private fun callLoginTutorApi(loginInfo: LoginInfo) {
    compositeDisposable += userLoginService.tutorLogin(loginInfo)
        .performOnBackgroundOutputOnMain()
        .subscribe({
            onSuccessHttpResponse(tutorLoginState)
        }, {
            onErrorHttpResponse(tutorLoginState)
        })
}

LoginFragment.kt

override fun observeLiveData() {
    viewModel.tutorLoginState.observe(this, Observer {
        it.getContentIfNotHandled()?.let { state ->
            when (state) {
                is UiState.Progress -> {
                    if (state.isLoading) {
                        network_loading_indicator.visible()
                    } else {
                        network_loading_indicator.visibilityGone()
                    }
                }

                is UiState.Success -> {
                    val responseData: TutorInfo = state.successInfo.data?.tutorInfo!!
                    context?.showToast(responseData.tutorName.toString())
                }

                is UiState.Alert -> context?.showToast(state.alert)

                is UiState.Failure -> {
                    if (state.throwable is IOException) {
                        context?.showToast("Internet Connection Failed")
                    } else {
                        context?.showToast("Json Parsing Error")
                    }
                }
            }
        }
    })

只发生了Http调用。但是LiveData

的变化没有反应

根据之前的对话,您可以像这样处理 BaseViewModel 中的一次性事件并创建一个 LiveData 值 loader 以在 base 内部集中处理它,并且在每个API 调用 &a message 以在 Toast 中显示错误或这样处理:

BaseViewModel

abstract class BaseViewModel : ViewModel() {
    protected val compositeDisposable = CompositeDisposable()
    val loader: MutableLiveData<Boolean> by lazy { SingleLiveEvent<Boolean>() }
    val message: MutableLiveData<Message> by lazy { SingleLiveEvent<Message>() }
    override fun onCleared() {
        compositeDisposable.clear()
        super.onCleared()
    }
}

在 ViewModel 中,为响应保留一个 LiveData 并一次性使用它。只需在此处切换 loader 值,以便可以从 Fragment/Activity 观察到通过使用 doOnSubscribe()doOnTerminate() 切换加载程序可见性,如下所示(详细说明使用这个 RxJava 动作操作符可以找到 here)。基本上总结一下:

  • doOnSubscribe() — 修改源,以便在从其订阅者订阅时调用给定的操作。
  • doOnTerminate() — 在此 Observable 发出 onError 或 onCompleted 信号之前调用指定的操作。

LoginViewModel

private lateinit var disposableResponse: Disposable
val tutorLoginResponse = MutableLiveData<TutorLoginResponse>()

fun login() {
       disposableResponse = userLoginService.tutorLogin()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .doOnSubscribe { loader.value = true }
            .doOnTerminate { loader.value = false }
            .subscribe({
                    onRetrieveResponseSuccess(it)
                }, {
                    onRetrieveResponseError(it)
                })

        compositeDisposable.add(disposableResponse)
    }

private fun onRetrievePostListSuccess(response: TutorLoginResponse) {
        tutorLoginResponse.value = response
}

private fun onRetrievePostListError(error: Throwable) {
        message.value = ToastMessage(error.message) //ToastMessage is a simple utility class to show Toast
}

然后从您的 viewmodel 观察更新后的 LiveData 值 loader,并从您的 Activity/Fragment 切换加载程序在 UI 中的可见性,并像这样访问响应:

LoginFragment

viewModel.loader.observe(this, Observer {
            if(it) showLoader() //showLoader() is a simple method in BaseFragment which invokes .show() on your deafult or custom Lottie animated loader
            else hideLoader() //hideLoader() is a similar method for invoking .hide() on your loader
})

viewModel.tutorLoginResponse.observe(this, Observer { response ->
            //do whatever with your response that's been returned here
})