Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change start destination of a navigation graph programmatically [Jetpack]

Basically, I have the following navigation graph:

enter image description here

I want to change my starting point in navigation graph to fragment 2 right after reaching it (in order to prevent going back to fragment 1 when pressing back button - like with the splash screen).

This is my code:

navGraph = navController.getGraph();
navGraph.setStartDestination(R.id.fragment2);
navController.setGraph(navGraph);

But, obviously it's not working and it gets back to fragment 1 after pressing back button.

Am I doing it wrong? Is there any other solution?

like image 402
Shynline Avatar asked Jul 04 '18 11:07

Shynline


People also ask

How do you set NAV graph programmatically?

You can write in your Activity as follows. Show activity on this post. findNavController() will search for controller in all child views and will return first controller, that is not null. If you have nested nav_graph s, your code can set graph to the wrong controller.

What is popUpToInclusive?

popUpTo and popUpToInclusive For example, if your app has an initial login flow, once a user has logged in, you should pop all of the login-related destinations off of the back stack so that the Back button doesn't take users back into the login flow.

What is launchSingleTop?

launchSingleTop. var launchSingleTop: Boolean. Whether this navigation action should launch as single-top (i.e., there will be at most one copy of a given destination on the top of the back stack). This functions similarly to how android. content.


4 Answers

UPDATE:

When you have nav graph like this:

<fragment
    android:id="@+id/firstFragment"
    android:name="com.appname.package.FirstFragment" >
    <action
        android:id="@+id/action_firstFragment_to_secondFragment"
        app:destination="@id/secondFragment" /> 
</fragment>

<fragment
    android:id="@+id/secondFragment"
    android:name="com.appname.package.SecondFragment"/>

And you want to navigate to the second fragment and make it root of your graph, specify the next NavOptions:

NavOptions navOptions = new NavOptions.Builder()
        .setPopUpTo(R.id.firstFragment, true)
        .build();

And use them for the navigation:

Navigation.findNavController(view).navigate(R.id.action_firstFragment_to_secondFragment, bundle, navOptions);

setPopUpTo(int destinationId, boolean inclusive) - Pop up to a given destination before navigating. This pops all non-matching destinations from the back stack until this destination is found.

destinationId - The destination to pop up to, clearing all intervening destinations.

inclusive - true to also pop the given destination from the back stack.


ALTERNATIVE:

<fragment
    android:id="@+id/firstFragment"
    android:name="com.appname.package.FirstFragment" >
<action
    android:id="@+id/action_firstFragment_to_secondFragment"
    app:destination="@id/secondFragment"
    app:popUpTo="@+id/firstFragment"
    app:popUpToInclusive="true" /> 
</fragment>

<fragment
    android:id="@+id/secondFragment"
    android:name="com.appname.package.SecondFragment"/>

And then on your code:

findNavController(fragment).navigate(
    FirstFragmentDirections.actionFirstFragmentToSecondFragment())

Old answer

Deprecated: The clearTask attribute for actions and the associated API in NavOptions has been deprecated.

Source: https://developer.android.com/jetpack/docs/release-notes


If you want to change your root fragment to fragment 2 (e.g. after pressing back button on fragment 2 you will exit the app), you should put the next attribute to your action or destination:

app:clearTask="true"

Practically it looks in a next way:

<fragment
    android:id="@+id/firstFragment"
    android:name="com.appname.package.FirstFragment"
    android:label="fragment_first" >
    <action
        android:id="@+id/action_firstFragment_to_secondFragment"
        app:destination="@id/secondFragment"
        app:clearTask="true" /> 
</fragment>

<fragment
    android:id="@+id/secondFragment"
    android:name="com.appname.package.SecondFragment"
    android:label="fragment_second"/>

I've added app:clearTask="true" to action.


Now when you perform navigation from fragment 1 to fragment 2 use the next code:

Navigation.findNavController(view)
        .navigate(R.id.action_firstFragment_to_secondFragment);
like image 172
Alexey Denysenko Avatar answered Oct 24 '22 07:10

Alexey Denysenko


I found a solution for this, but it's ugly. I guess this it to be expected with an alpha library, but I hope Google looks into simplifying/fixing this as this is a pretty popular navigation pattern.

Alexey's solution did not work for me. My problem was that I have up arrows showing on my Actionbar by using:

NavigationUI.setupActionBarWithNavController(this, navController)

If I did as Alexey suggests above, my new start fragment still had a arrow pointing to my initial start fragment. If I pressed that up arrow my app would sort-of restart, transitioning to itself (the new start fragment)

Here is the code needed to get to what I wanted which was:

  • Fragment #1 is where my application initially starts
  • I can do an Auth check in Fragment #1 and then programmatically change the start to fragment #2.
  • Once in Fragment #2 there is no up arrow and pressing the back button does not take you to Fragment #1.

Here is the code that accomplishes this. In my Activity's onCreate:

// Setup the toolbar
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(false)

// Configure the navigation
val navHost = nav_host_fragment as NavHostFragment
graph = navHost.navController
        .navInflater.inflate(R.navigation.nav_graph)
graph.startDestination = R.id.welcomeFragment

// This seems to be a magical command. Not sure why it's needed :(
navHost.navController.graph = graph

NavigationUI.setupActionBarWithNavController(this, navHost.navController)

and also:

fun makeHomeStart(){
    graph.startDestination = R.id.homeFragment
}

Then in Fragment #1's onActivityCreated, per Alexey's suggestion:

override fun onActivityCreated(savedInstanceState: Bundle?) {
    ...

    // Check for user authentication
    if(sharedViewModel.isUserAuthenticated()) {

        (activity as MainActivity).makeHomeStart() //<---- THIS is the key
        val navOptions = NavOptions.Builder()
                .setPopUpTo(R.id.welcomeFragment, true)
                .build()
        navController.navigate(R.id.action_welcomeFragment_to_homeFragment,null,navOptions)

    } else {
        navController.navigate(R.id.action_welcomeFragment_to_loginFragment)
    }
}

The key code is: (activity as MainActivity).makeHomeStart() which just runs a method in the activity that changes the graphs startDestination. I could clean this up and turn it into an interface, but I'll wait for Google and hope they improve this whole process. The method 'setPopUpTo' seems poorly named to me and it's not intuitive that your naming the fragment that is getting cut out of the graph. It's also strange to me that they're making these changes in navOptions. I would think navOptions would only relate to the navigation action they're connected to.

And I don't even know what navHost.navController.graph = graph does, but without it the up arrows return. :(

I'm using Navigation 1.0.0-alpha06.

like image 39
szaske Avatar answered Oct 24 '22 07:10

szaske


In MainActivity.kt

    val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val inflater = navHostFragment.navController.navInflater
    val graph = inflater.inflate(R.navigation.booking_navigation)

    if (isTrue){
        graph.startDestination = R.id.DetailsFragment
    }else {
        graph.startDestination = R.id.OtherDetailsFragment
    }

    val navController = navHostFragment.navController
    navController.setGraph(graph, intent.extras)

Remove startDestination from nav_graph.xml

?xml version="1.0" encoding="utf-8"?>
<!-- app:startDestination="@id/oneFragment" -->

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

  <fragment
    android:id="@+id/DetailFragment"
    android:name="DetailFragment"
    android:label="fragment_detail"
    tools:layout="@layout/fragment_detail"/>
  <fragment
    android:id="@+id/OtherDetailFragment"
    android:name="OtherDetailFragment"
    android:label="fragment_other_detail"
    tools:layout="@layout/fragment_other_detail"/>
</navigation>
like image 30
Dino Sunny Avatar answered Oct 24 '22 05:10

Dino Sunny


You can also try the followings.

val navController = findNavController(R.id.nav_host_fragment)
if (condition) {
    navController.setGraph(R.navigation.nav_graph_first)
} else {
    navController.setGraph(R.navigation.nav_graph_second)
}

Instead of trying to pop start destination or navigate manually to target destination, it would be better to have another navigation graph with different workflow. This would be even better for the case when you want completely different navigation flow conditionally.

like image 14
Doctiger Avatar answered Oct 24 '22 05:10

Doctiger