javaandroidandroid-fragmentsandroid-appcompatappcompatactivity

Android inflate layout crashes after adding fragment container view


currently I have a custom drawer activity that extends AppCompatActivity, which looks like the following:

public class DrawerActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener{
    
    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.setContentView(R.layout.activity_drawer);

        ...
    }


    @Override
    public void setContentView(int layoutResID) {
        frameLayout = findViewById(R.id.drawer_frame);
        LayoutInflater.from(getApplicationContext()).inflate(layoutResID, frameLayout);
        super.setContentView(drawerLayout);
    }

    ...
}

As you can see here, I overwrite the setContentView method, so the layout id of the activity that extend this drawer activity will be passed into this method and inflated in the drawer activity's subview. In a simple word, I want all activities that extend this drawer activity will have a navigation drawer. It works well for general acitivity.

But now, I have an activity that contains a fragment container view and extend the drawer activity. When I launch this activity, the application crashes with the following error:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.aaa, PID: 10730
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.aaa/com.example.aaa.gym.GymActivity}: android.view.InflateException: Binary XML file line #29 in com.example.aaa:layout/activity_gym: Binary XML file line #29 in com.example.aaa:layout/activity_gym: Error inflating class androidx.fragment.app.FragmentContainerView
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3635)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3792)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2210)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.app.ActivityThread.main(ActivityThread.java:7839)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
     Caused by: android.view.InflateException: Binary XML file line #29 in com.example.aaa:layout/activity_gym: Binary XML file line #29 in com.example.aaa:layout/activity_gym: Error inflating class androidx.fragment.app.FragmentContainerView
     Caused by: android.view.InflateException: Binary XML file line #29 in com.example.aaa:layout/activity_gym: Error inflating class androidx.fragment.app.FragmentContainerView
     Caused by: java.lang.reflect.InvocationTargetException
        at java.lang.reflect.Constructor.newInstance0(Native Method)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
        at android.view.LayoutInflater.createView(LayoutInflater.java:858)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:1010)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:965)
        at android.view.LayoutInflater.rInflate(LayoutInflater.java:1127)
        at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:1088)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:686)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:538)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:485)
        at com.example.aaa.DrawerActivity.setContentView(DrawerActivity.java:57)
        at com.example.aaa.gym.GymActivity.onCreate(GymActivity.java:62)
        at android.app.Activity.performCreate(Activity.java:8051)
        at android.app.Activity.performCreate(Activity.java:8031)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1329)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3608)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3792)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2210)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.app.ActivityThread.main(ActivityThread.java:7839)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
     Caused by: java.lang.UnsupportedOperationException: FragmentContainerView must be within a FragmentActivity to use android:name="com.example.aaa.gym.PlaceFragment"
E/AndroidRuntime:     at androidx.fragment.app.FragmentContainerView.<init>(FragmentContainerView.java:142)
        at androidx.fragment.app.FragmentContainerView.<init>(FragmentContainerView.java:120)
            ... 28 more

As you can see here, the root cause is java.lang.UnsupportedOperationException: FragmentContainerView must be within a FragmentActivity to use android:name="com.example.aaa.gym.PlaceFragment". However, the activity should be able to inflate fragments as it extends DrawerActivity and DrawerActivity extends AppCompatActivity, which extends Fragment Activity. I'm very confused about this.

Here is the activity code:

public class GymActivity extends DrawerActivity implements OnMapReadyCallback, GoogleMap.OnMarkerClickListener {

    private ActivityGymBinding binding;
    
    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        markers = new ArrayList<>();

        binding = ActivityGymBinding.inflate(getLayoutInflater());

        // here crashes, the actual crash line is the second line of the drawer activity's setContentView
        setContentView(binding.getRoot().getSourceLayoutResId()); 

        ...

    }
    
    ...
}

And this is the activity xml

<?xml version="1.0" encoding="utf-8"?>

<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/mapLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".gym.GymActivity">


    <com.google.android.gms.maps.MapView
        android:id="@+id/mapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:contentDescription="@string/title_activity_gym"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/place_fragment"
        android:name="com.example.aaa.gym.PlaceFragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:layout="@layout/place_fragment" />

</androidx.constraintlayout.widget.ConstraintLayout>

And the fragment, nearly nothing changed

public class PlaceFragment extends Fragment {

    private PlaceViewModel mViewModel;
    private PlaceFragmentBinding binding;

    public static PlaceFragment newInstance() {
        return new PlaceFragment();
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {

        mViewModel = new ViewModelProvider(requireActivity()).get(PlaceViewModel.class);

        mViewModel.getName().observe(getViewLifecycleOwner(), s -> binding.name.setText(s));
        mViewModel.getLocation().observe(getViewLifecycleOwner(), s -> binding.location.setText(s));

        return inflater.inflate(R.layout.place_fragment, container, false);
    }
}

fragment.xml, just a simple framelayout

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".gym.PlaceFragment">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/name"
        android:text="Hello" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/location"
        android:text="Hello" />

</FrameLayout>

Can anyone see why my application crashes? Any suggestion would be appreciated.


Solution

  • The problem is here:

    LayoutInflater.from(getApplicationContext()).inflate(layoutResID, frameLayout);
    

    You are using the application context - that is specifically not your Activity and hence, does not use the Activity's theme, nor does your Application class extend FragmentActivity. You'll want to use LayoutInflater.from(this) to actually use your Activity as the layout inflater.

    However, while that will fix your crash, your GymActivity still won't work because of these lines:

    binding = ActivityGymBinding.inflate(getLayoutInflater());
    
    // here crashes, the actual crash line is the second line of the drawer activity's setContentView
    setContentView(binding.getRoot().getSourceLayoutResId()); 
    

    Here, you create a binding object, but never actually add it your Activity. Instead, you call through to your setContentView override that inflates a brand new instance of that View. This means that any changes to your binding won't ever be reflected in your UI.

    Instead, you'd want to provide an override of your setContentView that takes a View and calls addView on your FrameLayout to add the already inflated View to your layout.

    @Override
    public void setContentView(View view) {
        frameLayout = findViewById(R.id.drawer_frame);
        frameLayout.addView(view)
        // Note you should never be calling super.setContentView here.
        // You've already called it in your own onCreate()
    }
    

    Which lets you use it like:

    binding = ActivityGymBinding.inflate(getLayoutInflater());
    
    setContentView(binding.getRoot());