Фрагмент навигации Android Jetpack отображается снова и снова

Я разрабатываю приложение для Android, используя библиотеку Jetpack:

  • Рукоять
  • Навигация
  • ViewModel
  • привязка данных

На самом деле, я знаком с шаблоном MVP.

Я пытаюсь изучить шаблон MVVP (Databinding и Jetpack ViewModel)

У меня есть 2 фрагмента (А и Б).

import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs

@AndroidEntryPoint
class AFragment {

    private val viewModel: AViewModel by viewModels()

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

        binding.viewModel = viewModel

    with(binding) {
        button.setOnClickListener {
            [email protected]()
        }
    }

    viewModel.result.observe(viewLifecycleOwner) { result ->
        findNavController().navigate(AFragmentDirections.actionAFragmentToBFragment(result))
    }
  }
}

А вот и AViewModel:

@HiltViewModel
class AViewModel @Inject constructor(): ViewModel() {

    private val _result: MutableLiveData<Int> = MutableLiveData()
    val result: LiveData<Int>
        get() = _result

    fun doAction() {
        _result.postValue(SOME_ACTION_RESULT)
    }
}

Он правильно показывает BFragment. Но если я коснусь Back Button на BFragment, он все равно покажет BFragment. На самом деле, он вернулся к AFragment, но снова возвращается к BFragment.

Когда я касаюсь Back Button на BFragment,

  1. AFragment снова запускается (я проверил, что onViewCreated() снова вызывается)
  2. Ниже код наблюдения вызывается снова:
viewModel.result.observe(viewLifecycleOwner) { result ->
    findNavController().navigate(AFragmentDirections.actionAFragmentToBFragment(result))
}

Почему этот код вызывается снова?

И правильно ли я пишу код?

Какова наилучшая практика?


Теперь я нашел решение.

Во фрагменте:

viewModel.result.observe(viewLifecycleOwner) { result ->
    if (result != null) {
        findNavController().navigate(AFragmentDirections.actionAFragmentToBFragment(result))
        viewModel.resetResult()
    }
}

и в AViewModel:

fun resetResult() {
    _result.postValue(null)
}

С этим кодом все работает.

Да... Но мне не нравится этот код...

Это... так странно...

Я не знаю, что является лучшей практикой...


person yoonhok    schedule 02.08.2021    source источник
comment
Поместите точку останова на результат, чтобы проверить, изменился ли он после нажатия кнопки «Назад». Каждый раз, когда он изменяется, будет вызываться код для перехода к другому фрагменту. Если это так, почему бы вам не использовать логическое значение, чтобы определить, должно ли оно перемещаться или нет?   -  person Dr4ke the b4dass    schedule 02.08.2021
comment
Функция doAction() просто вызывается один раз и больше никогда не будет вызываться.   -  person yoonhok    schedule 02.08.2021


Ответы (1)


проблема связана с живыми данными и жизненным циклом фрагментов.

AFragment и AViewModel живут, когда вы перемещаетесь в FragmentB, но представление в AFragment отсоединяется и прикрепляется, когда вы перемещаетесь и возвращаетесь. Это означает, что onViewCreated() вызывается каждый раз, когда вы нажимаете кнопку «Назад» на BFragment. В результате AFragment начинает наблюдать за AViewModel, которая уже имеет действительные данные с ее _result livedata.

Вы должны разделить uidata и события в livedata. Самым простым решением является реализация SingleEventLiveData и ее использование.

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

в модели представления:

private val _result: MutableLiveData<Event<Int>> = MutableLiveData()
val result: LiveData<Event<Int>>
    get() = _result

fun doAction() {
    _result.postValue(Event(5))
}

как наблюдать:

viewModel.result.observe(viewLifecycleOwner) { result ->
result.getContentIfNotHandled()?.let {
    findNavController().navigate(R.id.action_fragment_a_to_fragment_b)
}

}

источники: https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150

Как создать LiveData который генерирует одно событие и уведомляет только последнего подписанного наблюдателя?

person kiroglue    schedule 04.08.2021