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