Deno Agüero
Deno Agüero

Reputation: 599

Using Navigation Component inside BaseFragment

In my application I have an OptionsMenu which is used by many Fragments. Therefore I created the following BaseFragment:

abstract class BaseFragment : Fragment() {

private lateinit var navController: NavController

protected fun askForPermissions(permissionRequestCode: Int, vararg permissions: String) {
    requestPermissions(permissions, permissionRequestCode)
}

protected fun checkPermission(permission: String, activity: Activity): Boolean = when {
    Build.VERSION.SDK_INT < Build.VERSION_CODES.M -> true // It is not needed at all as there were no runtime permissions yet
    else -> ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED
}

override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
    inflater!!.inflate(R.menu.menu_topbar, menu)

    @SuppressLint("RestrictedApi")
    if (menu is MenuBuilder) {
        menu.setOptionalIconsVisible(true)
    }

    super.onCreateOptionsMenu(menu, inflater)
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    navController = Navigation.findNavController(view!!)

    return when (item.itemId) {
        R.id.menu_topbar_profile -> {
            navController.navigate(actionBaseToProfile())
            true
        }
        R.id.menu_topbar_favorites -> {
            navController.navigate(actionBaseToFavorites())
            true
        }
        R.id.menu_topbar_help -> {
            navController.navigate(actionBaseToHelp())
            true
        }
        else -> false
    }
}

}

And in the navigation file I added the BaseFragment and the target Fragments. Here is a snippet:

    <fragment
    android:id="@+id/fragment_base"
    android:name="com.test.scanner.base.BaseFragment">

    <action
        android:id="@+id/action_base_to_profile"
        app:destination="@id/fragment_profile" />
</fragment>

<fragment
    android:id="@+id/fragment_profile"
    android:name="com.test.scanner.ui.profile.ProfileFragment"
    tools:layout="@layout/fragment_profile" />

As you can see I don't defined a layout for the BaseFragment because there is no one. I always get an IllegalArgumentException if I want to navigate to a specific screen (e.g. ProfileFragment) by clicking on an element of the OptionsMenu. Here is the exception:

java.lang.IllegalArgumentException: navigation destination com.test.scanner.debug:id/action_base_to_profile is unknown to this NavController

How we can use the Navigation Component inside a BaseFragment. Is it possible at all or is there another solution for this case?

Upvotes: 2

Views: 1824

Answers (3)

John Zhang
John Zhang

Reputation: 21

This seems to be a great solution to the issue (and works for me):

public class MyFragmentFactory extends FragmentFactory{
    @NonNull
    @Override
    public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
        if (className.equalsIgnoreCase("com.proj.MyBaseFragment")){
            return MyFragmentFactory.getMyFragment();
        }else {
            return super.instantiate(classLoader, className);
        }
    }
}

In your activity onCreated method, put the following

    getSupportFragmentManager().setFragmentFactory(new MyFragmentFactory());

Bascally you work along with fargement manager but take over instantiations of certain fragments that only you know to create based on runtime situation.

Upvotes: 0

Maksym
Maksym

Reputation: 375

If you use SaveArgs in your project, to navigate from the 'BaseFragment' to other (not Abstract) fragments do this steps:

1) in your navigation.xml add 'android:id' for 'navigation' tag like this:

<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/destination_lists">

2) add an 'action' without 'fragment' inside your 'navigation' tag:

<action
    android:id="@+id/itemContent"
    app:destination="@id/destination_item_content" />

3) rebuild the project

4) use just generated class 'MobileNavigation'. The name came from your id for the 'navigation' tag.

val direction = MobileNavigationDirections.itemContent()
findNavController().navigate(direction)

Please read additional info here: Use Safe Args with a global action

Upvotes: 2

Kebab Krabby
Kebab Krabby

Reputation: 1699

You are mixing things. Dont put abstract class into your navigation graph but only classes which extends this BaseFragment class in your case. I would suggest you to start over with this tutorial navigation-architecture-components and once you finish it try to read more about abstract classes and then simplify solution from this article. When you will be done try to add menu but dont put it into baseFragment but rather put different menu into each fragment with only one action to next fragment. This will be easier. After you will wrap your head around it add new things like buttons.

Upvotes: 4

Related Questions