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

时间:2019-10-21 08:26:31

标签: android kotlin retrofit mutablelivedata

当我通过ViewModel中的方法传递其(MutableLiveData)实例的引用时,在Fragment中未观察到

MutableLiveData的状态。仅http调用发生,并且值显示在日志中,没有观察到与LiveData状态相对应的操作

我想调用Http方法,该方法的逻辑写在LoginViewModel中,该方法是BaseViewModel的子类。在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的更改没有任何反应

1 个答案:

答案 0 :(得分:1)

根据先前的对话,您可以像这样在BaseViewModel中处理一次性对象,并创建LiveData值loader以在基础内部集中处理它,并在每个API调用中重用相同的单个实时事件和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
}

然后从您的视图模型中观察loader的更新LiveData值,并从“活动/片段”中切换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
})