Есть ли какое-либо решение, чтобы наблюдать состояние MutubleLiveData в View, в то время как оно изменяется через параметр метода в ViewModel? - PullRequest
1 голос
/ 21 октября 2019

Состояние MutableLiveData не наблюдается во Fragment, пока я передаю ссылку на его экземпляр (MutableLiveData) через метод во ViewModel. Выполняется только HTTP-вызов, и значение отображается в журнале, НИКАКОГО действия, соответствующего состоянию LiveData, не наблюдается

Я хочу вызвать метод Http, логика которого записана в LoginViewModel, который является дочерним классом BaseViewModel. В BaseViewModel я создал несколько распространенных методов, которые принимают MutableLiveData в качестве параметра, вызывают этот метод в методе LoginViewModel и наблюдают за этими 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 Ответ

1 голос
/ 23 октября 2019

Исходя из предыдущего разговора, вы можете обрабатывать одноразовые вещи в BaseViewModel следующим образом и создавать значение LiveData loader, чтобы обрабатывать его централизованно внутри базы, а также повторно использовать одно и то же живое событие в каждом вызове 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 можно найти здесь ). В основном, чтобы подвести итог:

  • doOnSubscribe() - изменяет источник так, чтобы он вызывал указанное действие, когда он подписан от своих подписчиков.
  • doOnTerminate() - вызывает указанныйдействие непосредственно перед этим наблюдаемым сигналом 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
}

Затем наблюдайте обновленное значение LiveData loader из вашей модели представления и переключайте видимость вашего загрузчика в пользовательском интерфейсе из вашей деятельности / фрагмента, а такжеполучить доступ к ответу, как это:

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
})

...