Android崩溃onCreate() – Fragment无法转换为androidx.navigation.fragment.NavHostFragment

huangapple go评论78阅读模式
英文:

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 {
    // ... (remaining code)
}

Fragment 1

public class Fragment_1 extends Fragment {
    // ... (remaining code)
}

Fragment 2

public class Fragment_2 extends Fragment {
    // ... (remaining code)
}

activity_main.xml

<!-- ... (remaining code) -->

mobile_navigation.xml

<!-- ... (remaining code) -->

main_drawer.xml

<!-- ... (remaining code) -->

AndroidManifest.xml

<!-- ... (remaining code) -->

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.

英文:

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(&quot;Fragment 1&quot;);
                break;
            }
            case R.id.nav_fragment_2: {
                fragmentManager.beginTransaction()
                        .replace(R.id.nav_host_fragment, new Fragment_2())
                        .commitNow();
                MainActivity.this.setTitle(&quot;Fragment 2&quot;);
                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 -&gt; {
            Intent intent = new Intent(Intent.ACTION_SEND);
            intent.setType(&quot;text/plain&quot;);
            intent.putExtra(Intent.EXTRA_SUBJECT, &quot;Subject&quot;);
            intent.putExtra(Intent.EXTRA_TEXT, &quot;Message&quot;);
            startActivity(Intent.createChooser(intent, &quot;Title&quot;));
        });

        return root;
    }
}

activity_main.xml

&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.drawerlayout.widget.DrawerLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:id=&quot;@+id/drawer_layout&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    android:fitsSystemWindows=&quot;true&quot;
    tools:openDrawer=&quot;start&quot;
    &gt;

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

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

mobile_navigation.xml

&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;navigation xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    app:startDestination=&quot;@+id/nav_fragment_1&quot;&gt;

    &lt;fragment
        android:id=&quot;@+id/nav_fragment_1&quot;
        android:name=&quot;com.example.problemtesting.Fragments.Fragment_1&quot;
        tools:layout=&quot;@layout/fragment_1&quot;
        /&gt;
    &lt;fragment
        android:id=&quot;@+id/nav_fragment_2&quot;
        android:name=&quot;com.example.problemtesting.Fragments.Fragment_2&quot;
        tools:layout=&quot;@layout/fragment_2&quot;
        /&gt;
&lt;/navigation&gt;

main_drawer.xml

&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;menu xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    tools:showIn=&quot;navigation_view&quot;&gt;

    &lt;group android:checkableBehavior=&quot;single&quot;&gt;
        &lt;item
            android:id=&quot;@+id/nav_fragment_1&quot;
            android:title=&quot;Fragment 1&quot; /&gt;
        &lt;item
            android:id=&quot;@+id/nav_fragment_2&quot;
            android:title=&quot;Fragment 2&quot; /&gt;
    &lt;/group&gt;
&lt;/menu&gt;

AndroidManifest.xml

&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    package=&quot;com.example.problemtesting&quot;&gt;

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

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.

答案1

得分: 3

问题在于您的 onNavigationItemSelected 正在执行一个 FragmentTransaction,用一个片段替换整个 NavHostFragment。这始终是在使用 NavHostFragment 时更改片段的错误方法 - 您应始终是 导航到目标

然而,在您的情况下,您做的工作比实际上必要的要多得多。相反,您应该:

  1. 完全删除您的 OnNavigationItemSelectedListener 代码。当您调用 NavigationUI.setupWithNavController(navigationView, navController); 时,导航已经为您设置了一个,其中包括关闭抽屉并根据 Material 设计规范使用正确的淡入淡出动画。

  2. 根据 顶部应用栏指南,您不应该手动设置活动的标题,而是在图形中的每个目标上添加一个 android:label,然后您已经执行的 setupActionBarWithNavController 调用将为您执行此操作。

英文:

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.

huangapple
  • 本文由 发表于 2020年9月10日 22:05:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/63831423.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定