Проблема с фрагментом Android и ViewModel

#android #fragment #viewmodel

#Android #фрагмент #модель просмотра #viewmodel

Вопрос:

Я создал FragmentA и инициализировал ViewModel внутри onViewCreated(). Я прикрепил наблюдателя тем же методом.

Во FragmentA я выполняю вызов API и в случае успешного выполнения вызова API заменяю FragmentA на FragmentB на addToBackStack.

Теперь настоящая проблема начинается, когда я нажимаю кнопку «Назад» во FragmentB, вызывается FragmentA в заднем стеке, но сразу же снова заменяется FragmentB.

 class FragmentA : Fragment(){
private var viewModel: TrackingViewModel?=null

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    initViewModel()
    attachObservers()
}

private fun initViewModel(){
    viewModel = ViewModelProvider(requireActivity()).get(TrackingViewModel::class.java)
}

private fun attachObservers() {
    viewModel?.mResult?.observe(viewLifecycleOwner, {
        it?.let { resource ->
            parseResource(resource)
        }
    })
}

//Called this method on Button CLick in UI
private fun validate(data:String){
    viewModel?.coroutineSearch(data)
}

private fun parseResource(resource: Resource<GetsApiResponse>) {
    when (resource.status) {
        Status.SUCCESS -> {
            showLoading(false)
            //replaceFragmentWithBackStack is an Extension function
            replaceFragmentWithBackStack(FragmentB(), R.id.container)
        }
        Status.ERROR -> {
            showLoading(false)
            infoError(resource.responseCode)
        }
        Status.EXCEPTION -> {
            showLoading(false)
            infoException()
        }
        Status.LOADING -> {
            showLoading(true)
        }
    }
}
  

}

Комментарии:

1. используете ли вы Навигационный компонент?

2. @A.R.B.N Я не использую Навигационный компонент.

Ответ №1:

В какой-то момент это общая проблема для всех, кто работает с LiveData . Проблема здесь вызвана преднамеренным LiveData поведением: оно возвращает вам сохраненное значение (если таковое имеется), как только вы начинаете его наблюдать. LiveData был разработан в первую очередь для использования с решениями для просмотра / привязки данных. Как только компонент пользовательского интерфейса начнет наблюдение LiveData , он должен получить значение и отобразить его соответствующим образом. Таким образом, поведение, которое вы получаете, является преднамеренным.

Многие другие разработчики, включая меня, сталкивались именно с этой проблемой. Я смог решить ее, используя event wrapper, рекомендованный в качестве решения этой проблемы, которую я нашел в этом сообщении. Это просто и доступно для понимания и работает так, как описано в сообщении.

С помощью этой оболочки события ваш код наблюдателя обновится до:

 private fun attachObservers() {
    viewModel?.mResult?.observe(viewLifecycleOwner, {
        it?.getContentIfNotHandled()?.let { resource ->
            // Only proceed if the event has never been handled
            parseResource(resource)
        }
    })
}
  

Если вам интересно, почему в первую очередь вы сразу получаете результат от этого LiveData — это потому, что ваша модель представления была кэширована. Когда вы используете activity в качестве хранилища модели представления или владельца магазина ( ViewModelProvider(requireActivity()) ), ваша модель представления будет работать до тех пор, пока используемое вами действие не будет уничтожено. Это означает, что даже если вы уйдете FragmentA , нажав кнопку «Назад» к предыдущему фрагменту, а затем вернетесь обратно к FragmentA , создав новый экземпляр, вы получите ту же модель представления.

Исходный код оболочки события

 /**
 * Used as a wrapper for data that is exposed via a LiveData that represents an event.
 */
open class Event<out T>(private val content: T) {

    var hasBeenHandled = false
        private set // Allow external read but not write

    /**
     * Returns the content and prevents its use again.
     */
    fun getContentIfNotHandled(): T? {
        return if (hasBeenHandled) {
            null
        } else {
            hasBeenHandled = true
            content
        }
    }

    /**
     * Returns the content, even if it's already been handled.
     */
    fun peekContent(): T = content
}