Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Activity not showing using NavController

I have a java app for android using bottom navigation activity. The application worked well, but the state of the fragments was not saved during the transition. To solve this problem, I decided to make my own FragmentNavigator. Now when I use my fragment instead of the standard fragment in xml layout, I get the following error:

E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.wasd, PID: 13588
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.mobileapp/com.example.mobileapp.AccountMainActivity}: android.view.InflateException: Binary XML file line #23: Binary XML file line #23: Error inflating class fragment
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2817)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
    at android.app.ActivityThread.-wrap11(Unknown Source:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
    at android.os.Handler.dispatchMessage(Handler.java:105)
    at android.os.Looper.loop(Looper.java:164)
    at android.app.ActivityThread.main(ActivityThread.java:6541)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
 Caused by: android.view.InflateException: Binary XML file line #23: Binary XML file line #23: Error inflating class fragment
 Caused by: android.view.InflateException: Binary XML file line #23: Error inflating class fragment
 Caused by: java.lang.RuntimeException: Exception inflating com.example.mobileapp:navigation/mobile_navigation line 12
    at androidx.navigation.NavInflater.inflate(NavInflater.java:97)
    at androidx.navigation.NavController.setGraph(NavController.java:557)
    at androidx.navigation.NavController.setGraph(NavController.java:539)
    at androidx.navigation.fragment.NavHostFragment.onCreate(NavHostFragment.java:248)
    at androidx.fragment.app.Fragment.performCreate(Fragment.java:2981)
    at androidx.fragment.app.FragmentStateManager.create(FragmentStateManager.java:474)
    at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:257)
    at androidx.fragment.app.FragmentLayoutInflaterFactory.onCreateView(FragmentLayoutInflaterFactory.java:142)
    at androidx.fragment.app.FragmentController.onCreateView(FragmentController.java:135)
    at androidx.fragment.app.FragmentActivity.dispatchFragmentsOnCreateView(FragmentActivity.java:295)
    at androidx.fragment.app.FragmentActivity.onCreateView(FragmentActivity.java:274)
    at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:780)
    at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:730)
    at android.view.LayoutInflater.rInflate(LayoutInflater.java:863)
    at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:515)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:374)
    at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:706)
    at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:195)
    at com.example.mobileapp.AccountMainActivity.onCreate(AccountMainActivity.java:32)
    at android.app.Activity.performCreate(Activity.java:6975)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1213)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2770)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
    at android.app.ActivityThread.-wrap11(Unknown Source:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
    at android.os.Handler.dispatchMessage(Handler.java:105)
    at android.os.Looper.loop(Looper.java:164)
    at android.app.ActivityThread.main(ActivityThread.java:6541)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
 Caused by: java.lang.IllegalStateException: Could not find Navigator with name "improved_fragment". You must call NavController.addNavigator() for each navigation type.
    at androidx.navigation.NavigatorProvider.getNavigator(NavigatorProvider.java:98)
    at androidx.navigation.NavInflater.inflate(NavInflater.java:107)
    at androidx.navigation.NavInflater.inflate(NavInflater.java:141)
    at androidx.navigation.NavInflater.inflate(NavInflater.java:88)
        ... 32 more

The main activity looks like this:

package com.example.mobileapp;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;

import com.example.mobileapp.databinding.AccountMainActivityBinding;
import com.example.mobileapp.ui.ImprovedFragmentNavigator;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.navigation.NavigationBarView;

public class AccountMainActivity extends AppCompatActivity {

private Handler mHandler = new Handler();
private AccountMainActivityBinding binding;

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

    BottomNavigationView navView = findViewById(R.id.nav_view);
    AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
            R.id.navigation_user, R.id.navigation_orders, R.id.navigation_products, R.id.navigation_companies, R.id.navigation_map)
            .build();
    NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment_activity_main);

    final NavController navController = navHostFragment.getNavController();
    navController.getNavigatorProvider().addNavigator(new ImprovedFragmentNavigator(this, navHostFragment.getChildFragmentManager(), R.id.nav_host_fragment_activity_main));
    navController.setGraph(R.navigation.mobile_navigation);

    NavigationUI.setupWithNavController(navView, navController);

    navView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            int id = item.getItemId();
            if (item.isChecked()) return false;

            switch (id)
            {
                case R.id.navigation_user :
                    navController.navigate(R.id.action_global_navigation_user);
                    break;
                case R.id.navigation_orders :
                    navController.navigate(R.id.action_global_navigation_orders);
                    break;
                case R.id.navigation_products :
                    navController.navigate(R.id.action_global_navigation_products);
                    break;
                case R.id.navigation_companies :
                    navController.navigate(R.id.action_global_navigation_companies);
                    break;
                case R.id.navigation_map :
                    navController.navigate(R.id.action_global_navigation_map);
                    break;
            }
            return true;
        }
    });
}
}

The main activity XML looks like this:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

<?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/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".AccountMainActivity">

<fragment
    android:id="@+id/nav_host_fragment_activity_main"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    app:defaultNavHost="true"
    app:layout_constraintBottom_toTopOf="@id/nav_view"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintVertical_bias="0.0"
    app:navGraph="@navigation/mobile_navigation" />

<com.google.android.material.bottomnavigation.BottomNavigationView
    android:id="@+id/nav_view"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_below="@+id/nav_host_fragment_activity_main"
    android:background="?android:attr/windowBackground"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:menu="@menu/bottom_nav_menu" />

</androidx.constraintlayout.widget.ConstraintLayout>

The navigation file looks like this:

<?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/navigation_user">

<improved_fragment
    android:id="@+id/navigation_user"
    android:name="com.example.mobileapp.ui.user.UserFragment"
    android:label="@string/title_user"
    tools:layout="@layout/fragment_user" />

<improved_fragment
    android:id="@+id/navigation_orders"
    android:name="com.example.mobileapp.ui.orders.OrdersFragment"
    android:label="@string/title_orders"
    tools:layout="@layout/fragment_orders" />

<improved_fragment
    android:id="@+id/navigation_products"
    android:name="com.example.mobileapp.ui.products.ProductsFragment"
    android:label="@string/title_products"
    tools:layout="@layout/fragment_products" />

<improved_fragment
    android:id="@+id/navigation_companies"
    android:name="com.example.mobileapp.ui.companies.CompaniesFragment"
    android:label="@string/title_companies"
    tools:layout="@layout/fragment_companies" />

<improved_fragment
    android:id="@+id/navigation_map"
    android:name="com.example.mobileapp.ui.map.MapFragment"
    android:label="@string/title_map"
    tools:layout="@layout/fragment_map" />

<action
    android:id="@+id/action_global_navigation_user"
    app:destination="@id/navigation_user"
    app:launchSingleTop="true"
    app:popUpTo="@id/navigation_user" />

<action
    android:id="@+id/action_global_navigation_orders"
    app:destination="@id/navigation_orders"
    app:launchSingleTop="true"
    app:popUpTo="@id/navigation_orders" />

<action
    android:id="@+id/action_global_navigation_products"
    app:destination="@id/navigation_products"
    app:launchSingleTop="true"
    app:popUpTo="@id/navigation_products" />

<action
    android:id="@+id/action_global_navigation_companies"
    app:destination="@id/navigation_companies"
    app:launchSingleTop="true"
    app:popUpTo="@id/navigation_companies" />

<action
    android:id="@+id/action_global_navigation_map"
    app:destination="@id/navigation_map"
    app:launchSingleTop="true"
    app:popUpTo="@id/navigation_map" />

</navigation>

And finally my fragment file looks like this:

package com.example.mobileapp.ui;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import androidx.annotation.CallSuper;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.navigation.NavDestination;
import androidx.navigation.NavOptions;
import androidx.navigation.Navigator;
import androidx.navigation.NavigatorProvider;
import androidx.navigation.fragment.FragmentNavigator;

import com.example.mobileapp.R;

import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

@Navigator.Name("improved_fragment")
public class ImprovedFragmentNavigator extends Navigator<ImprovedFragmentNavigator.Destination>{

private static final String TAG = "ImprovedFragmentNavigator";
private static final String KEY_BACK_STACK_IDS = "androidx-nav-fragment:navigator:backStackIds";

private final Context mContext;
@SuppressWarnings("WeakerAccess") /* synthetic access */
final FragmentManager mFragmentManager;
private final int mContainerId;
@SuppressWarnings("WeakerAccess") /* synthetic access */
        ArrayDeque<Integer> mBackStack = new ArrayDeque<>();
@SuppressWarnings("WeakerAccess") /* synthetic access */
        boolean mIsPendingBackStackOperation = false;

public ImprovedFragmentNavigator(@NonNull Context context, @NonNull FragmentManager manager,
                                 int containerId) {
    mContext = context;
    mFragmentManager = manager;
    mContainerId = containerId;
}

@Override
public boolean popBackStack() {
    if (mBackStack.isEmpty()) {
        return false;
    }
    if (mFragmentManager.isStateSaved()) {
        Log.i(TAG, "Ignoring popBackStack() call: FragmentManager has already"
                + " saved its state");
        return false;
    }
    if (mFragmentManager.getBackStackEntryCount() > 0) {
        mFragmentManager.popBackStack(
                generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
                FragmentManager.POP_BACK_STACK_INCLUSIVE);
        mIsPendingBackStackOperation = true;
    } // else, we're on the first Fragment, so there's nothing to pop from FragmentManager
    mBackStack.removeLast();
    return true;
}

@NonNull
@Override
public ImprovedFragmentNavigator.Destination createDestination() {
    return new ImprovedFragmentNavigator.Destination(this);
}

@Deprecated
@NonNull
public Fragment instantiateFragment(@NonNull Context context,
                                    @NonNull FragmentManager fragmentManager,
                                    @NonNull String className, @SuppressWarnings("unused") @Nullable Bundle args) {
    return fragmentManager.getFragmentFactory().instantiate(
            context.getClassLoader(), className);
}

@Nullable
@Override
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
                               @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
    if (mFragmentManager.isStateSaved()) {
        Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
                + " saved its state");
        return null;
    }
    String className = destination.getClassName();
    if (className.charAt(0) == '.') {
        className = mContext.getPackageName() + className;
    }

    final FragmentTransaction ft = mFragmentManager.beginTransaction();

    int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
    int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
    int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
    int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
    if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
        enterAnim = enterAnim != -1 ? enterAnim : 0;
        exitAnim = exitAnim != -1 ? exitAnim : 0;
        popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
        popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
        ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
    }

    String tag = Integer.toString(destination.getId());
    Fragment primaryNavigationFragment = mFragmentManager.getPrimaryNavigationFragment();
    if(primaryNavigationFragment != null)
        ft.hide(primaryNavigationFragment);
    Fragment destinationFragment = mFragmentManager.findFragmentByTag(tag);
    if(destinationFragment == null) {
        destinationFragment = instantiateFragment(mContext, mFragmentManager, className, args);
        destinationFragment.setArguments(args);
        ft.add(mContainerId, destinationFragment , tag);
    }
    else
        ft.show(destinationFragment);

    ft.setPrimaryNavigationFragment(destinationFragment);

    final @IdRes int destId = destination.getId();
    final boolean initialNavigation = mBackStack.isEmpty();
    // TODO Build first class singleTop behavior for fragments
    final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
            && navOptions.shouldLaunchSingleTop()
            && mBackStack.peekLast() == destId;

    boolean isAdded;
    if (initialNavigation) {
        isAdded = true;
    } else if (isSingleTopReplacement) {
        // Single Top means we only want one instance on the back stack
        if (mBackStack.size() > 1) {
            // If the Fragment to be replaced is on the FragmentManager's
            // back stack, a simple replace() isn't enough so we
            // remove it from the back stack and put our replacement
            // on the back stack in its place
            mFragmentManager.popBackStackImmediate(
                    generateBackStackName(mBackStack.size(), mBackStack.peekLast()), 0);
            mIsPendingBackStackOperation = false;
        }
        isAdded = false;
    } else {
        ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
        mIsPendingBackStackOperation = true;
        isAdded = true;
    }
    if (navigatorExtras instanceof FragmentNavigator.Extras) {
        FragmentNavigator.Extras extras = (FragmentNavigator.Extras) navigatorExtras;
        for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) {
            ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
        }
    }
    ft.setReorderingAllowed(true);
    ft.commit();
    // The commit succeeded, update our view of the world
    if (isAdded) {
        mBackStack.add(destId);
        return destination;
    } else {
        return null;
    }
}

@Override
@Nullable
public Bundle onSaveState() {
    Bundle b = new Bundle();
    int[] backStack = new int[mBackStack.size()];
    int index = 0;
    for (Integer id : mBackStack) {
        backStack[index++] = id;
    }
    b.putIntArray(KEY_BACK_STACK_IDS, backStack);
    return b;
}

@Override
public void onRestoreState(@Nullable Bundle savedState) {
    if (savedState != null) {
        int[] backStack = savedState.getIntArray(KEY_BACK_STACK_IDS);
        if (backStack != null) {
            mBackStack.clear();
            for (int destId : backStack) {
                mBackStack.add(destId);
            }
        }
    }
}

@NonNull
private String generateBackStackName(int backStackIndex, int destId) {
    return backStackIndex + "-" + destId;
}

private int getDestId(@Nullable String backStackName) {
    String[] split = backStackName != null ? backStackName.split("-") : new String[0];
    if (split.length != 2) {
        throw new IllegalStateException("Invalid back stack entry on the "
                + "NavHostFragment's back stack - use getChildFragmentManager() "
                + "if you need to do custom FragmentTransactions from within "
                + "Fragments created via your navigation graph.");
    }
    try {
        // Just make sure the backStackIndex is correctly formatted
        Integer.parseInt(split[0]);
        return Integer.parseInt(split[1]);
    } catch (NumberFormatException e) {
        throw new IllegalStateException("Invalid back stack entry on the "
                + "NavHostFragment's back stack - use getChildFragmentManager() "
                + "if you need to do custom FragmentTransactions from within "
                + "Fragments created via your navigation graph.");
    }
}

@SuppressWarnings("WeakerAccess") /* synthetic access */
boolean isBackStackEqual() {
    int fragmentBackStackCount = mFragmentManager.getBackStackEntryCount();
    // Initial fragment won't be on the FragmentManager's back stack so +1 its count.
    if (mBackStack.size() != fragmentBackStackCount + 1) {
        return false;
    }

    // From top to bottom verify destination ids match in both back stacks/
    Iterator<Integer> backStackIterator = mBackStack.descendingIterator();
    int fragmentBackStackIndex = fragmentBackStackCount - 1;
    while (backStackIterator.hasNext() && fragmentBackStackIndex >= 0) {
        int destId = backStackIterator.next();
        try {
            int fragmentDestId = getDestId(mFragmentManager
                    .getBackStackEntryAt(fragmentBackStackIndex--)
                    .getName());
            if (destId != fragmentDestId) {
                return false;
            }
        } catch (NumberFormatException e) {
            throw new IllegalStateException("Invalid back stack entry on the "
                    + "NavHostFragment's back stack - use getChildFragmentManager() "
                    + "if you need to do custom FragmentTransactions from within "
                    + "Fragments created via your navigation graph.");
        }
    }

    return true;
}

@NavDestination.ClassType(Fragment.class)
public static class Destination extends NavDestination {

    private String mClassName;

    public Destination(@NonNull NavigatorProvider navigatorProvider) {
        this(navigatorProvider.getNavigator(ImprovedFragmentNavigator.class));
    }

    public Destination(@NonNull Navigator<? extends ImprovedFragmentNavigator.Destination> fragmentNavigator) {
        super(fragmentNavigator);
    }

    @CallSuper
    @Override
    public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs) {
        super.onInflate(context, attrs);
        TypedArray a = context.getResources().obtainAttributes(attrs,
                R.styleable.FragmentNavigator);
        String className = a.getString(R.styleable.FragmentNavigator_android_name);
        if (className != null) {
            setClassName(className);
        }
        a.recycle();
    }

    @NonNull
    public final ImprovedFragmentNavigator.Destination setClassName(@NonNull String className) {
        mClassName = className;
        return this;
    }

    @NonNull
    public final String getClassName() {
        if (mClassName == null) {
            throw new IllegalStateException("Fragment class was not set");
        }
        return mClassName;
    }

    @NonNull
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(super.toString());
        sb.append(" class=");
        if (mClassName == null) {
            sb.append("null");
        } else {
            sb.append(mClassName);
        }
        return sb.toString();
    }
}

public static final class Extras implements Navigator.Extras {
    private final LinkedHashMap<View, String> mSharedElements = new LinkedHashMap<>();

    Extras(Map<View, String> sharedElements) {
        mSharedElements.putAll(sharedElements);
    }

    @NonNull
    public Map<View, String> getSharedElements() {
        return Collections.unmodifiableMap(mSharedElements);
    }

    public static final class Builder {
        private final LinkedHashMap<View, String> mSharedElements = new LinkedHashMap<>();

        @NonNull
        public ImprovedFragmentNavigator.Extras.Builder addSharedElements(@NonNull Map<View, String> sharedElements) {
            for (Map.Entry<View, String> sharedElement : sharedElements.entrySet()) {
                View view = sharedElement.getKey();
                String name = sharedElement.getValue();
                if (view != null && name != null) {
                    addSharedElement(view, name);
                }
            }
            return this;
        }

        @NonNull
        public ImprovedFragmentNavigator.Extras.Builder addSharedElement(@NonNull View sharedElement, @NonNull String name) {
            mSharedElements.put(sharedElement, name);
            return this;
        }

        @NonNull
        public ImprovedFragmentNavigator.Extras build() {
            return new ImprovedFragmentNavigator.Extras(mSharedElements);
        }
    }
}
}

>Solution :

As per the documentation:

Caution: When manually calling setGraph(), note the following:

  • Don’t use the app:navGraph element when adding the NavHostFragment in XML.
  • Don’t call NavHostFragment.create(@NavigationRes int).
  • Don’t use any other APIs that rely solely on the R.navigation ID to inflate and set your graph.

So just remove your app:navGraph attribute from your XML file to only set your graph via setGraph after your addition of your custom Navigator.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading