I know this question seems to be asked and answered, but it is not true. Because I haven't met a solution yet.
I have Activity with a lot of fragments. And I store all transitions history (can return to each fragment in LIFO order with the back press - because each adds to back stack)
I want to reach the next feature: When I press back - view of bottom fragment must not be re-creating.
I do next
1) Use android navigation components and transitions like
fun navigate(@IdRes resId: Int, bundle: Bundle? = null, navOptions: NavOptions? = null, sharedElements: List<View>? = null) {
navController.navigate(resId, bundle, navOptions, sharedElements?.let {
if (it.isEmpty()) null else
FragmentNavigatorExtras(
*it.map { view -> Pair(view, view.transitionNameCompat.safe) }
.filter { pair -> pair.second.isNotEmpty() }
.toTypedArray()
)
})
}
where idRes is destination id (not transition id)
2) hold the content view in fragment and detach it from the parent in onCreateView view method. Because getView()
returns null even fragment appear from back stack.
private var contentView: View? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
prepareView(contentView) ?: createView(inflater, container)
private fun prepareView(view: View?): View? {
val parent = view?.parent
val viewGroup = parent as? ViewGroup
viewGroup?.removeView(view)
return view
}
protected open fun createView(inflater: LayoutInflater, container: ViewGroup?): View? {
val layout = layout()
if (layout == -1)
throw IllegalArgumentException("You need to override \"layout()\" fun or override \"createView\" fun")
return inflater.inflate(layout(), container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
if (contentView == null) {
onFirstInitialization(view, savedInstanceState)
} else {
onNextInitialization(view, savedInstanceState)
}
onEachInitialization(view, savedInstanceState)
contentView = view
}
I've got exception ONLY IF I transit to next fragment and press "back" rapidly! In normal mode all good and correct.
Exception:
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
at android.view.ViewGroup.addViewInner(ViewGroup.java:5050)
at android.view.ViewGroup.addView(ViewGroup.java:4881)
at android.view.ViewGroup.addView(ViewGroup.java:4821)
at android.view.ViewGroup.addView(ViewGroup.java:4794)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:890)
at androidx.fragment.app.FragmentManagerImpl.addAddedFragments(FragmentManagerImpl.java:2092)
at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1866)
at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1822)
at androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentManagerImpl.java:1723)
at androidx.fragment.app.FragmentManagerImpl.dispatchStateChange(FragmentManagerImpl.java:2624)
at androidx.fragment.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManagerImpl.java:2580)
at androidx.fragment.app.Fragment.performActivityCreated(Fragment.java:2571)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:907)
at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1235)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1301)
at androidx.fragment.app.FragmentManagerImpl.dispatchStateChange(FragmentManagerImpl.java:2620)
at androidx.fragment.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManagerImpl.java:2580)
at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:246)
at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:541)
at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:201)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1470)
at android.app.Activity.performStart(Activity.java:7176)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3086)
at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:180)
at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:165)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:142)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1926)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6923)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
I can solve this by place prepareView
to onDestroyView
lifecycle method and everything work correctly, but it produces memory leak: I find it out - when the view has not a parent after onDestroyView
then onDestroy
never called and fragments hold in memory even after the back press.
You can say "Hey dude, don't hurry, use your app slowly and the exception will never be thrown, as you say. But app in production and I met crashes with this bug" :( How I can handle this case?
UPDATE: I researched a little and found out something. Exception thrown only when view not detached from window. And view detach from window only after 300-400 ms after new Fragment opened
Yes! It's true. You can't create again its object because it has a parent. The main problem may you haven't noticed is whenever you called your fragment again then onCreateView()
is called.
So, in-sort I suggest you move your code from onCreateView()
to onCreate()
method of the fragment.
I also struggled with this type of problem. So, inflate your view in onCreate()
method and return main view in onCreateView()
method.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.