Мое приложение Kotlin аварийно завершает работу, когда я меняю фрагменты, позвольте мне объяснить.
В моем приложении есть несколько фрагментов, которые являются моими страницами. В своих фрагментах я ссылаюсь на представление XML с помощью viewBinding вот так:
private var binding: MyFragmentBinding by autoCleared()
Я сделал несколько вызовов API и другую обработку в onViewCreated (которая работает отлично), пока все не стало хорошо, но теперь, если я нажму кнопку «Назад» на телефоне до того, как элементы завершат загрузку, приложение вылетит с ошибкой:
FATAL EXCEPTION: main (Ask Gemini)
Process: fr.trecobat.brick, PID: 6594
java.lang.IllegalStateException: should never call auto-cleared-value get when it might not be available
at fr.trecobat.brick.utils.AutoClearedValue.getValue(AutoClearedValue.kt:32)
fr.trecobat.brick.ui.itineraire_chantiers.ItineraireChantiersFragment.getBinding(ItineraireChantiersFragment.kt:198)
Проблема в том, что я пытаюсь получить доступ к привязке, когда фрагмент уничтожен.
Точно так же, если спамить кнопку назад телефона, то думаю, что предыдущий фрагмент не успевает загрузиться и возвращаюсь к предыдущему фрагменту, поэтому приложение вылетает с такой ошибкой:
FATAL EXCEPTION: main (Ask Gemini)
Process: fr.trecobat.brick, PID: 26518
java.lang.IllegalArgumentException: Navigation action/destination fr.trecobat.brick:id/action_tourneeFragment_to_selectionChantierFragment cannot be found from the current destination Destination(fr.trecobat.brick:id/selectionChantierFragment) label=SelectionChantierFragment class=fr.trecobat.brick.ui.selection_chantier.SelectionChantierFragment
at fr.trecobat.brick.ui.tournee.TourneeFragment$onViewCreated$3.handleOnBackPressed(TourneeFragment.kt:176)
На данный момент я попробовал эти два решения.
Я заменил строку, которая автоматически очищает привязку, на эту:
private var binding: MyFragmentBinding by notNull()
Кажется, это работает, но я теряю самоочистку и потенциально подвергаюсь утечкам памяти, верно?
Для второго варианта в моем onBackPressedDispatcher я установил таймер:
requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (canNavigate) {
canNavigate = false
handler.postDelayed({ canNavigate = true }, 300) // 300ms
findNavController().navigate(
R.id.action_itineraireChantiersFragment_to_tourneeAffairesFragment,
bundleOf(
"touId" to touId,
)
)
}else{
Timber.e("Stop back")
}
}
}
)
Это позволяет избежать агрессивного переключения страниц, но является ли это правильным решением?
Есть ли у вас лучшие решения моих проблем?




В этой документации представители Android учат вас, как самостоятельно определять утечку памяти, чтобы им не приходилось отвечать на каждый тестовый пример, который может выполнить пользователь. Кроме того, вы можете использовать LeakCanary, который отлично справляется с обнаружением утечек памяти.
Представьте, что у вас есть фрагмент и он имеет ссылки на некоторые свойства. Нам нужно очистить ссылки на эти поля, присвоив значение null в методе жизненного цикла onDestroyView(), чтобы Fragment не содержал ссылок на них.
Итак, в вашем случае нет необходимости использовать autoCleared() и notNull(), вы можете попробовать что-то простое:
private var _binding: ProfileBinding? = null
private val binding get() = _binding!!
private val mAdapter = MyListAdapter()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = ProfileBinding.inflate(inflater, container, false)
val view = binding.root
binding.recyclerView.adapter = mAdapter
return view
}
override fun onDestroyView() {
super.onDestroyView()
binding.recyclerView.adapter = null
_binding = null
}
Да, именно, вы правы, но основная идея в том, что Фрагменты переживают свои взгляды. поэтому обязательно удалите все ссылки на экземпляр класса привязки в методе onDestroyView() фрагмента.
Проблема в том, что я пытаюсь получить доступ к привязке, когда фрагмент уничтожен.
Да, вызовы API могут занять некоторое время, и они могут оставаться активными, даже если ваш фрагмент будет уничтожен. Когда они завершат работу и попытаются получить доступ к привязке, она может оказаться нулевой, поскольку ваш фрагмент уже может быть уничтожен.
Кажется, это работает, но я теряю самоочистку и потенциально подвергаюсь утечкам памяти, верно?
Да, из-за того, как были разработаны фрагменты, они могут пережить свои представления и могут быть повторно использованы с другим представлением. Поэтому нам нужно обнулить привязку при уничтожении фрагмента.
Рекомендуемое решение
private class YourViewModel : ViewModel() {
private val _apiResult = MutableStateFlow<String?>(null)
val apiResult get() = _apiResult.asStateFlow()
fun loadDataFromApi() {
val hello = "world" // load data from api
_apiResult.update { hello }
}
}
И в твоем фрагменте
private val viewModel by viewModels<YourViewModel>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
viewModel.apiResult.collect { apiResult ->
// set data to your views here
}
}
}
}
viewModel.loadDataFromApi()
}
Вы можете проверить, жив ли фрагмент и безопасен ли он для использования привязки, прежде чем получить доступ к привязке.
fun Fragment.isSafe(): Boolean {
return !(this.isRemoving || this.activity == null || this.isDetached || !this.isAdded || this.view == null)
}
Что касается второй проблемы, то, похоже, для ее выявления требуется больше контекста. Возможно, этот ответ поможет.
Нет смысла очищать адаптер таким образом. Помните, что вся структура представления (включая RecyclerView) выбрасывается.