Why parent of fragment is null after pause?

Viewed 156

When I run app and use it no crash. BottomSheetBehavior works correctly. But if I return to app after a long time pause in background, it crashing with null point exception when try to cast view.parent as View in order to find View with BottomSheetBehavior.

Why fragment don't have parent after pause? How to fix it?

I tried to do binding.root.parent as View and other requireActivity().findViewById(R.id.fragment_sheet_container). But the crash with same case continuous.

@AndroidEntryPoint
class ToolboxSheetFragment : Fragment(R.layout.fragment_toolbox_sheet) {
    private lateinit var binding: FragmentToolboxSheetBinding
    private lateinit var behavior: BottomSheetBehavior<View>

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

        binding = FragmentToolboxSheetBinding.bind(view)
        behavior = BottomSheetBehavior.from(
            view.parent as View // null cannot be cast to non-null type android.view.View
        )
    }

The parent of fragment is main activity with the next layout:

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".view.MainActivity">

    // ...

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragment_sheet_container"
        android:name=".view.fragment.ToolboxSheetFragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:behavior_hideable="false"
        app:behavior_peekHeight="?actionBarSize"
        app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
        tools:layout="@layout/fragment_toolbox_sheet" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>
java.lang.RuntimeException: Unable to start activity ComponentInfo{
.view.MainActivity}: java.lang.NullPointerException: null cannot be cast to non-null type android.view.View
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2792)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2870)
        at android.app.ActivityThread.-wrap11(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1601)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:172)
        at android.app.ActivityThread.main(ActivityThread.java:6590)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
     Caused by: java.lang.NullPointerException: null cannot be cast to non-null type android.view.View
        at .view.fragment.ToolboxSheetFragment.onViewCreated(ToolboxSheetFragment.kt:46)
        at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:3128)
        at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:552)
        at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261)
        at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1424)
        at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2968)
        at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:2886)
        at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:263)
        at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:351)
        at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:246)
        at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1335)
        at android.app.Activity.performStart(Activity.java:7043)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2755)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2870) 
        at android.app.ActivityThread.-wrap11(Unknown Source:0) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1601) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:172) 
        at android.app.ActivityThread.main(ActivityThread.java:6590) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
3 Answers

It would be better if I could get full project. But I am suggesting couple of ways here:

Way 1:

How about keeping behavior inside activity class. instantiate it onCreate(). And use it from ToolboxSheetFragment like this.

class MainActivity : BaseActivity() {
    var behavior: BottomSheetBehavior? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        behavior = BottomSheetBehavior.from(R.id.sheet_id)
    }
}

Then from ToolboxSheetFragment

class ToolboxSheetFragment : Fragment(R.layout.fragment_toolbox_sheet) {
    private lateinit var binding: FragmentToolboxSheetBinding
    var behavior: BottomSheetBehavior? = null

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

        binding = FragmentToolboxSheetBinding.bind(view)
        behavior = (requireActivity() as MainActivity).behavior
    }

Way 2:

Using addOnAttachStateChangeListener on ToolboxSheetFragment:

@AndroidEntryPoint
class ToolboxSheetFragment : Fragment(R.layout.fragment_toolbox_sheet) {
    private lateinit var binding: FragmentToolboxSheetBinding
    private lateinit var behavior: BottomSheetBehavior<View>

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

        view.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
            override fun onViewAttachedToWindow(v: View?) {
                if(v!=null){
                    binding = FragmentToolboxSheetBinding.bind(v)
                    behavior = BottomSheetBehavior.from(
                        v.parent as View 
                    )
                }
            }

            override fun onViewDetachedFromWindow(v: View?) {
                view.removeOnAttachStateChangeListener(this)
            }

        })
    }

First inflate the layout in onCreateView() and you can do all your view related work in onViewCreated().

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View {
    binding = FragmentToolboxSheetBinding.inflate(layoutInflater)
    return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    behavior = BottomSheetBehavior.from(view)
}

Do not ignore savedInstanceState and make sure that the Fragment is even attached to any Context. Sorry for not providing a fully featured answer; just stating the relevant conditions, which the other answers also did not consider. eg. onAttached() one can be certain that the Fragment is attached - and savedInstanceState may or may not be null. And when view.parent is null, the View might not be there. It's possibly the behavior of the MainActivity and not the ToolboxSheetFragment, which is not the same thing.

Related