ViewBindingPropertyDelegate
ViewBindingPropertyDelegate copied to clipboard
Clear happens after onViewCreated
Description:
If you quickly add and remove a fragment from the back stack, then there is a situation where onViewDestroyed is called after onViewCreated. This causes onViewCreated to use the old view. The problem is floating and reproduces over time.
I think the problem is that postClear
of onDestroy
from viewLifecycle
doesn't guarantee that clear
will be called before onViewCreated
.
Source code:
class MainActivity : AppCompatActivity(R.layout.activity_main) {
fun onFragmentAClick() {
supportFragmentManager.executePendingTransactions()
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, FragmentB())
.setTransition(TRANSIT_FRAGMENT_FADE)
.addToBackStack(null)
.commit()
}
fun onFragmentBClick() {
supportFragmentManager.executePendingTransactions()
supportFragmentManager.popBackStack()
}
}
class FragmentA : Fragment(R.layout.fragment_a) {
private val viewBinding by viewBinding(
vbFactory = FragmentABinding::bind,
onViewDestroyed = { Log.d(LOG_TAG, "FragmentA onViewDestroyed binding = $it") }
)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.d(LOG_TAG, "FragmentA onViewCreated binding = $viewBinding")
viewBinding.root.text = "Fragment A onViewCreated"
viewBinding.root.setOnClickListener { (context as? MainActivity)?.onFragmentAClick() }
}
}
class FragmentB : Fragment(R.layout.fragment_b) {
private val viewBinding by viewBinding(
vbFactory = FragmentBBinding::bind,
onViewDestroyed = { Log.d(LOG_TAG, "FragmentB onViewDestroyed binding = $it") }
)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.d(LOG_TAG, "FragmentB onViewCreated binding = $viewBinding")
viewBinding.root.text = "Fragment B onViewCreated"
viewBinding.root.setOnClickListener { (context as? MainActivity)?.onFragmentBClick() }
}
}
private const val LOG_TAG = "ViewBindingDelegate"
<!--activity_main.xml-->
<androidx.fragment.app.FragmentContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:name=".FragmentA"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<!--fragment_a.xml-->
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?colorPrimary"
android:gravity="center"
android:text="Fragment A"
android:textColor="?colorOnPrimary"
/>
<!--fragment_b.xml-->
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?colorSecondary"
android:gravity="center"
android:text="Fragment B"
android:textColor="?colorOnSecondary"
/>
Logs:
FragmentB onViewCreated binding = FragmentBBinding@d1b9dfd
FragmentA onViewDestroyed binding = FragmentABinding@a96db92
FragmentA onViewCreated binding = FragmentABinding@10bb7ab
FragmentB onViewDestroyed binding = FragmentBBinding@d1b9dfd
FragmentB onViewCreated binding = FragmentBBinding@51e195a
FragmentA onViewCreated binding = FragmentABinding@10bb7ab
FragmentA onViewDestroyed binding = FragmentABinding@10bb7ab
FragmentB onViewDestroyed binding = FragmentBBinding@51e195a
It can be seen that the FragmentA onViewCreated binding = FragmentABinding@10bb7ab
was called twice
I'm having the same problem. Took me hours already and I haven't even been able to solve it.
upd: in case you want to know how I fixed it.
The presentation logic was very poorly written (we had to publish the application ASAP) which is why in some cases there was a fragment opening and instantly closing without displaying anything. I changed the logic so that the fragment wouldn't be needlessly created and the whole problem was gone.
This sort of implies that this issue won't really need a fix.
Hi. It's interesting case. Need to add additional check of Fragment View state. Will be investigated
I had the same problem when I used Navigation
and fast forward and back. Let me first describe my situation:
My general logic in MainFragment
is as follows, and there are no other ViewBinding operations. Returned in BFragment
via findNavController().popBackStack()
.
//MainFragment
private val binding by viewBinding(FragmentMainBinding::bind, onViewDestroyed = {
printer.debug("onViewDestroyed;binding:${it.idPrint()}")
})
fun onViewCreated() {
printer.info("onViewCreated")
// set background is red,then background is white when happen bug。
binding.root.setBackgroundColor(Color.RED)
binding.root.setOnClickListener {
printer.debug("navigate to BFragment")
findNavController().navigate(R.id.bFragment, null, animOptions)
}
}
fun onDestroyView() {
printer.debug("onDestroyView")
}
Then I quickly switch MainFragment and BFragment, which is easier to reproduce . When an exception is encountered, its log is like this:
2023-11-08 14:56:42.769 I onViewCreated
2023-11-08 14:56:42.770 D useBinding:FragmentMainBinding@32292761
2023-11-08 14:56:42.770 I onViewCreated end
2023-11-08 14:56:42.770 V MainFragment@166602538 onStart
2023-11-08 14:56:42.772 V MainFragment@166602538 onResume
2023-11-08 14:56:42.918 D navigate to BFragment
2023-11-08 14:56:42.933 V MainFragment@166602538 onPause
2023-11-08 14:56:42.934 V MainFragment@166602538 onStop
2023-11-08 14:56:42.936 V BFragment@148110504 onCreate
2023-11-08 14:56:42.946 V BFragment@148110504 onStart
2023-11-08 14:56:42.961 V BFragment@227944255 onDestroy
2023-11-08 14:56:42.962 V BFragment@148110504 onResume
2023-11-08 14:56:43.085 D navigate popBackStack to AFragment
2023-11-08 14:56:43.096 V BFragment@148110504 onPause
2023-11-08 14:56:43.098 V BFragment@148110504 onStop
2023-11-08 14:56:43.098 V MainFragment@166602538 onStart
2023-11-08 14:56:43.103 V MainFragment@166602538 onResume
2023-11-08 14:56:43.273 V BFragment@148110504 onDestroy
2023-11-08 14:56:43.281 D navigate to BFragment
2023-11-08 14:56:43.298 V MainFragment@166602538 onPause
2023-11-08 14:56:43.299 V MainFragment@166602538 onStop
2023-11-08 14:56:43.301 V BFragment@248523369 onCreate
2023-11-08 14:56:43.313 V BFragment@248523369 onStart
2023-11-08 14:56:43.314 V BFragment@248523369 onResume
2023-11-08 14:56:43.453 D navigate popBackStack to AFragment
2023-11-08 14:56:43.457 V BFragment@248523369 onPause
2023-11-08 14:56:43.458 V BFragment@248523369 onStop
2023-11-08 14:56:43.459 V MainFragment@166602538 onStart
2023-11-08 14:56:43.463 V MainFragment@166602538 onResume
2023-11-08 14:56:43.632 V BFragment@248523369 onDestroy
2023-11-08 14:56:43.642 D navigate to BFragment
2023-11-08 14:56:43.657 V MainFragment@166602538 onPause
2023-11-08 14:56:43.659 V MainFragment@166602538 onStop
2023-11-08 14:56:43.661 V BFragment@199799963 onCreate
2023-11-08 14:56:43.673 V BFragment@199799963 onStart
2023-11-08 14:56:43.674 V BFragment@199799963 onResume
2023-11-08 14:56:43.827 D navigate popBackStack to AFragment
2023-11-08 14:56:43.829 D onDestroyView
2023-11-08 14:56:43.831 V BFragment@199799963 onPause
2023-11-08 14:56:43.832 V BFragment@199799963 onStop
2023-11-08 14:56:43.849 I onViewCreated
2023-11-08 14:56:43.850 D useBinding:FragmentMainBinding@32292761
2023-11-08 14:56:43.850 I onViewCreated end
2023-11-08 14:56:43.850 V MainFragment@166602538 onStart
2023-11-08 14:56:43.852 V MainFragment@166602538 onResume
2023-11-08 14:56:43.853 D onViewDestroyed;binding:FragmentMainBinding@32292761
2023-11-08 14:56:44.028 V BFragment@199799963 onDestroy
It can be found that after using FragmentMainBinding@32292761
last time, after frequent switching, onDestroyView
was called correctly, but this time onViewCreated
used the last FragmentMainBinding@32292761
before it happened onViewDestroyed
of ViewBindingPropertyDelegate
.
I read several previous issues and learned that you have fixed similar problems. My testing came to the following conclusions:
- If you do not use ViewBindingPropertyDelegate, but use
_binding
andonDesotoryView { _binding = null }
, it is normal - If animation is not used when switching Fragments (
animOptions
is set to null), it is also normal.
val animOptions = navOptions {
anim {
enter = androidx.navigation.ui.R.anim.nav_default_enter_anim
exit = androidx.navigation.ui.R.anim.nav_default_exit_anim
popEnter = androidx.navigation.ui.R.anim.nav_default_pop_enter_anim
popExit = androidx.navigation.ui.R.anim.nav_default_pop_exit_anim
}
}
I believe the problem lies with Fragment's transition animation, but I have no idea how to solve this problem on ViewBindingPropertyDelegate.