Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android crash onCreate() - Fragment cannot be cast to androidx.navigation.fragment.NavHostFragment

I decided to execute a Firebase Robo Test for my app and noticed that it always crashes after opening an external activity and then returning back to the app. I was able to duplicate the problem by enabling the "Don't keep activities" in the Developer options.

Crash: java.lang.ClassCastException: com.example.problemtesting.Fragments.Fragment_2 cannot be cast to androidx.navigation.fragment.NavHostFragment

It indicates that the problem is caused by this line of code: NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);

How to duplicate the problem:

  1. Enable "Don't keep activities"
  2. Open the app and then the app drawer
  3. Select "Fragment 2"
  4. Press on the Share button
  5. Press on the Messages button (or any other external app)
  6. Return to the app (Crash)

The app works fine if "Don't keep activities" is disabled.

MainActivity

public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {

    DrawerLayout drawer;
    NavigationView navigationView;
    FragmentManager fragmentManager;
    AppBarConfiguration mAppBarConfiguration;
    NavController navController;

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

        fragmentManager = getSupportFragmentManager();
        drawer = findViewById(R.id.drawer_layout);
        navigationView = findViewById(R.id.nav_view);

        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        mAppBarConfiguration = new AppBarConfiguration.Builder(
                R.id.nav_fragment_1, R.id.nav_fragment_2)
                .setOpenableLayout(drawer)
                .build();

        NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
        if (navHostFragment != null)
            navController = navHostFragment.getNavController();
        else navController = Navigation.findNavController(this, R.id.nav_host_fragment);

        NavigationUI.setupActionBarWithNavController(MainActivity.this, navController, mAppBarConfiguration);
        NavigationUI.setupWithNavController(navigationView, navController);

        setNavigationViewListener();
    }

    @Override
    public boolean onSupportNavigateUp() {
        return NavigationUI.navigateUp(navController, mAppBarConfiguration)
                || super.onSupportNavigateUp();
    }

    private void setNavigationViewListener() {
        navigationView.setNavigationItemSelectedListener(MainActivity.this);
    }

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
        switch (menuItem.getItemId()) {
            case R.id.nav_fragment_1: {
                fragmentManager.beginTransaction()
                        .replace(R.id.nav_host_fragment, new Fragment_1())
                        .commitNow();
                MainActivity.this.setTitle("Fragment 1");
                break;
            }
            case R.id.nav_fragment_2: {
                fragmentManager.beginTransaction()
                        .replace(R.id.nav_host_fragment, new Fragment_2())
                        .commitNow();
                MainActivity.this.setTitle("Fragment 2");
                break;
            }
        }
        drawer.closeDrawers();
        return true;
    }
}

Fragment 1

public class Fragment_1 extends Fragment{

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

        container.removeAllViews();
        View root = inflater.inflate(R.layout.fragment_1, container, false);

        return root;
    }
}

Fragment 2

public class Fragment_2 extends Fragment {

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

        container.removeAllViews();
        View root = inflater.inflate(R.layout.fragment_2, container, false);

        Button share = root.findViewById(R.id.share);

        share.setOnClickListener(view -> {
            Intent intent = new Intent(Intent.ACTION_SEND);
            intent.setType("text/plain");
            intent.putExtra(Intent.EXTRA_SUBJECT, "Subject");
            intent.putExtra(Intent.EXTRA_TEXT, "Message");
            startActivity(Intent.createChooser(intent, "Title"));
        });

        return root;
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:openDrawer="start"
    >

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:tag="my_fragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/mobile_navigation"
        />

    <com.google.android.material.navigation.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/nav_header_main"
        app:menu="@menu/main_drawer"
        />
</androidx.drawerlayout.widget.DrawerLayout>

mobile_navigation.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation 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"
    app:startDestination="@+id/nav_fragment_1">

    <fragment
        android:id="@+id/nav_fragment_1"
        android:name="com.example.problemtesting.Fragments.Fragment_1"
        tools:layout="@layout/fragment_1"
        />
    <fragment
        android:id="@+id/nav_fragment_2"
        android:name="com.example.problemtesting.Fragments.Fragment_2"
        tools:layout="@layout/fragment_2"
        />
</navigation>

main_drawer.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:showIn="navigation_view">

    <group android:checkableBehavior="single">
        <item
            android:id="@+id/nav_fragment_1"
            android:title="Fragment 1" />
        <item
            android:id="@+id/nav_fragment_2"
            android:title="Fragment 2" />
    </group>
</menu>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.problemtesting">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name="com.example.problemtesting.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

It's a frustrating problem that I can't figure out. I have tried to remove the NavigationView.OnNavigationItemSelectedListener listener and it works fine, but I need that listener for other controls such as fragment switching animations and drawer layout transitions.

like image 408
Miha M Avatar asked Oct 15 '22 00:10

Miha M


1 Answers

The problem is that your onNavigationItemSelected is doing a FragmentTransaction, replacing the entire NavHostFragment with a Fragment. This is always the wrong way to change fragments when using a NavHostFragment - you should always be navigating to a destination.

However, in your case, you are doing way more work than is actually necessary. Instead, you should:

  1. Remove your OnNavigationItemSelectedListener code entirely. Navigation has already set one up for you when you call NavigationUI.setupWithNavController(navigationView, navController); That includes closing the drawer and using the correct cross fade animation as per the Material design spec.

  2. As per the top app bar guide, you should not be manually setting the title of the acctivity, but instead add an android:label to each destination in your graph and the setupActionBarWithNavController call you've done will do that for you.

like image 67
ianhanniballake Avatar answered Oct 20 '22 11:10

ianhanniballake