Dima
Dima

Reputation: 1249

BottomNavigationView creates new fragment with every click on tab (android.os.TransactionTooLargeException)

I have a BottomNavigationView with 5 items. Sometimes I got error android.os.TransactionTooLargeException.

I used TooLargeTool to find a problem and found out that it happens when app goes background if I click on tabs many time because every time a new fragment created and finally data parcel size becomes more than 1 mb which generates an exception.

      val navController = findNavController(R.id.nav_host_fragment_activity_main)




          WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = false


    navController.addOnDestinationChangedListener { _, destination, _ ->
        when (destination.id){
            R.id.rifleChoiceFragment -> {
                navView.visibility = View.GONE
                window.statusBarColor = defaultColor
                WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = true
            }
            R.id.ammoChoiceFragment -> {
                WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = true
                navView.visibility = View.GONE
                window.statusBarColor = defaultColor
            }
            R.id.dataBaseFragment-> {
                navView.visibility = View.GONE
                window.statusBarColor = defaultColor
                WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = true
            }
            R.id.caliberChoiceFragment-> {
                navView.visibility = View.GONE
                window.statusBarColor = defaultColor
                WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = true
            }
            R.id.vendorChoiceFragment-> {
                navView.visibility = View.GONE
                window.statusBarColor = defaultColor
                WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = true
            }
            R.id.secondAmmoChoiceFragment-> {
                navView.visibility = View.GONE
                window.statusBarColor = defaultColor
                WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = true
            }
            R.id.payWallFragment -> {
                navView.visibility = View.GONE
                window.statusBarColor = blackColor
                WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = false
            }
            R.id.splashFragment-> {
                navView.visibility = View.GONE
                window.statusBarColor = blackColor
                WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = false
            }
            else -> {
                WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = true
                navView.visibility = View.VISIBLE
                window.statusBarColor = defaultColor
            }
        }


    }
    navView.setupWithNavController(navController)

nav graph:

<?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"
android:id="@+id/mobile_navigation"
app:startDestination="@+id/splashFragment">

<fragment
    android:id="@+id/navigation_home"
    android:name="NAME"
    android:label="@string/title_home"
    tools:layout="@layout/fragment_ammo" >
    <action
        android:id="@+id/action_navigation_home_to_rifleChoiceFragment"
        app:destination="@id/rifleChoiceFragment"
        app:launchSingleTop="false" />
    <action
        android:id="@+id/action_navigation_home_to_ammoChoiceFragment"
        app:destination="@id/ammoChoiceFragment"
        app:launchSingleTop="false" />
</fragment>

<fragment
    android:id="@+id/navigation_dashboard"
    android:name="NAME"
    android:label="@string/title_dashboard"
    tools:layout="@layout/fragment_table" />

<fragment
    android:id="@+id/navigation_notifications"
    android:name="NAME"
    android:label="@string/Trajectory"
    tools:layout="@layout/fragment_plot" >
    <action
        android:id="@+id/action_navigation_notifications_to_secondAmmoChoiceFragment"
        app:destination="@id/secondAmmoChoiceFragment"
        app:launchSingleTop="false" />
</fragment>
<fragment
    android:id="@+id/navigation_target"
    android:name="NAME"
    android:label="@string/reticle"
    tools:layout="@layout/fragment_target" >
    <action
        android:id="@+id/action_navigation_target_to_reticleChoiceFragment"
        app:destination="@id/reticleChoiceFragment"
        app:launchSingleTop="false" />
    <action
        android:id="@+id/action_navigation_target_to_targetChoiceFragment"
        app:destination="@id/targetChoiceFragment"
        app:launchSingleTop="false" />
    <action
        android:id="@+id/action_navigation_target_to_payWallFragment"
        app:destination="@id/payWallFragment"
        app:launchSingleTop="true" />
</fragment>
<fragment
    android:id="@+id/navigation_settings"
    android:name="NAME t"
    android:label="@string/settings"
    tools:layout="@layout/settings_fragment" />
<fragment
    android:id="@+id/rifleChoiceFragment"
    android:name="NAME"
    android:label="rifle_choice_fragment"
    tools:layout="@layout/rifle_choice_fragment" />
<fragment
    android:id="@+id/ammoChoiceFragment"
    android:name="NAME"
    android:label="fragment_ammo_choice"
    tools:layout="@layout/fragment_ammo_choice" >
    <action
        android:id="@+id/action_ammoChoiceFragment_to_dataBaseFragment"
        app:destination="@id/dataBaseFragment"
        app:launchSingleTop="false" />
</fragment>
<fragment
    android:id="@+id/dataBaseFragment"
    android:name="NAME"
    android:label="fragment_data_base"
    tools:layout="@layout/fragment_data_base" >
    <action
        android:id="@+id/action_dataBaseFragment_to_caliberChoiceFragment"
        app:destination="@id/caliberChoiceFragment"
        app:launchSingleTop="false" />
    <action
        android:id="@+id/action_dataBaseFragment_to_vendorChoiceFragment"
        app:destination="@id/vendorChoiceFragment"
        app:launchSingleTop="false" />
</fragment>
<fragment
    android:id="@+id/caliberChoiceFragment"
    android:name="NAME"
    android:label="fragment_caliber_choice"
    tools:layout="@layout/fragment_caliber_choice" />
<fragment
    android:id="@+id/vendorChoiceFragment"
    android:name="NAME"
    android:label="fragment_vendor_choice"
    tools:layout="@layout/fragment_vendor_choice" />
<fragment
    android:id="@+id/secondAmmoChoiceFragment"
    android:name="NAME"
    android:label="fragment_second_ammo_choice"
    tools:layout="@layout/fragment_second_ammo_choice" >
    <action
        android:id="@+id/action_secondAmmoChoiceFragment_to_dataBaseFragment"
        app:destination="@id/dataBaseFragment" />
</fragment>
<fragment
    android:id="@+id/reticleChoiceFragment"
    android:name="NAME"
    android:label="fragment_reticle_choice"
    tools:layout="@layout/fragment_reticle_choice" />
<fragment
    android:id="@+id/targetChoiceFragment"
    android:name="NAME"
    android:label="fragment_target_choice"
    tools:layout="@layout/fragment_target_choice" />
<fragment
    android:id="@+id/payWallFragment"
    android:name="NAME"
    android:label="fragment_pay_wall"
    tools:layout="@layout/fragment_pay_wall" >
    <action
        android:id="@+id/action_payWallFragment_to_navigation_home"
        app:destination="@id/navigation_home"
        app:launchSingleTop="false"
        app:popUpTo="@id/mobile_navigation"
        app:popUpToInclusive="true" />
</fragment>
<fragment
    android:id="@+id/splashFragment"
    android:name="NAME"
    android:label="fragment_splash"
    tools:layout="@layout/fragment_splash" >
    <action
        android:id="@+id/action_splashFragment_to_navigation_home"
        app:destination="@id/navigation_home"
        app:launchSingleTop="false"
        app:popUpTo="@id/mobile_navigation"
        app:popUpToInclusive="true" />
    <action
        android:id="@+id/action_splashFragment_to_payWallFragment"
        app:destination="@id/payWallFragment"
        app:launchSingleTop="false"
        app:popUpTo="@id/mobile_navigation"
        app:popUpToInclusive="true" />
</fragment>


</navigation>

bottom nav menu:

<?xml version="1.0" encoding="utf-8"?>
<item
    android:id="@+id/navigation_home"
    android:icon="@drawable/ic_bulletsvg"
    android:title="@string/title_home" />

<item
    android:id="@+id/navigation_dashboard"
    android:icon="@drawable/ic_spreadsheet"
    android:title="@string/title_dashboard"
   />

<item
    android:id="@+id/navigation_notifications"
    android:icon="@drawable/ic_b"
    android:title="@string/Trajectory"
    />
<item
    android:id="@+id/navigation_targ"
    android:icon="@drawable/ic_ret"
    android:title="@string/ret"
    />

<item
    android:id="@+id/navigation_settings"
    android:icon="@drawable/ic_set"
    android:title="@string/settings"
    />

What's the proper way to fix it? Should I handle click on each tab manually?

Upvotes: 0

Views: 330

Answers (1)

ianhanniballake
ianhanniballake

Reputation: 199825

This is your problem:

app:startDestination="@+id/splashFragment"

As per the Principles of Navigation, you should be using your actual home screen as the start destination of your graph:

app:startDestination="@+id/navigation_home"

You should never be using a splash destination as your start destination - as per those exact same principles:

Note: An app might have a one-time setup or series of login screens. These conditional screens should not be considered start destinations because users see these screens only in certain cases.

This is how you should be implementing your paywall screen - by following the Conditional Navigation documentation to conditionally navigate from your actual start destination to the paywall screen.

Why is this the source of the problem with your bottom navigation? It is because setupByNavController pops up to the start destination of your app when swapping between tabs, thus ensuring that each tab is independent from one another. When the start destination of your app isn't on the back stack, there is no single point of reference in the back stack to know what to pop up to so what happens is that nothing gets popped at all - you just keep building up more and more and more of a back stack instead of replacing one back stack with another as you swap tabs. By keeping the start destination of your app on the back stack, setupWithNavController, which assumes you are following the Principles of Navigation, will work as intended.

You'll find that using a splash screen for your start destination also breaks any deep links you add to your graph as it is the start destination that is added to the back stack and you want your home screen to be on the back stack, not your splash screen.

Upvotes: 1

Related Questions