是否可以使用 Android 导航架构组件(Android Jetpack)有条件地设置 startDestination?

我使用 安卓喷气背包中的 导航来在屏幕之间导航。 现在我想动态设置 startDestination。

我有一个名为 MainActivity 的活动 还有两个片段,片段 A 和片段 B。

var isAllSetUp : Boolean = // It is dynamic and I’m getting this from Preferences.


If(isAllSetUp)
{
// show FragmentA
}
else
{
//show FragmentB
}

我想使用导航体系结构组件设置上述流。目前我使用的 startDestionation 如下所示,但它不能满足我的需求。

<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/lrf_navigation"
app:startDestination="@id/fragmentA">


<fragment
android:id="@+id/fragmentA"
android:name="com.mindinventory.FragmentA"
android:label="fragment_a"
tools:layout="@layout/fragment_a" />
</navigation>

是否可以使用 Android 导航体系结构组件有条件地设置 startDestination?

41293 次浏览

Finally, I got a solution to my query...

Put below code in onCreate() method of Activity.

Kotlin code

val navHostFragment = (supportFragmentManager.findFragmentById(R.id.home_nav_fragment) as NavHostFragment)
val inflater = navHostFragment.navController.navInflater
val graph = inflater.inflate(R.navigation.nav_main)
//graph.addArgument("argument", NavArgument)
graph.setStartDestination(R.id.fragment1)
//or
//graph.setStartDestination(R.id.fragment2)


navHostFragment.navController.graph = graph

Java code

NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.home_nav_fragment);  // Hostfragment
NavInflater inflater = navHostFragment.getNavController().getNavInflater();
NavGraph graph = inflater.inflate(R.navigation.nav_main);
//graph.addArgument("argument", NavArgument)
graph.setStartDestination(R.id.fragment1);


navHostFragment.getNavController().setGraph(graph);
navHostFragment.getNavController().getGraph().setDefaultArguments(getIntent().getExtras());




NavigationView navigationView = findViewById(R.id.navigationView);
NavigationUI.setupWithNavController(navigationView, navHostFragment.getNavController());

Additional Info

As @artnest suggested, remove the app:navGraph attribute from the layout. It would look something like this after removal

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:layout_width="match_parent"
android:layout_height="match_parent">


<androidx.fragment.app.FragmentContainerView
android:id="@+id/home_nav_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true" />


</FrameLayout>

In the case of a fragment tag used instead of FragmentContainerView, the above changes remain the same

Some of the APIs have changed, are unavailable or are not necessary since Akash's answer. It's a bit simpler now.

MainActivity.java:

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


NavHostFragment navHost = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_main_nav_host);
NavController navController = navHost.getNavController();


NavInflater navInflater = navController.getNavInflater();
NavGraph graph = navInflater.inflate(R.navigation.navigation_main);


if (false) {
graph.setStartDestination(R.id.oneFragment);
} else {
graph.setStartDestination(R.id.twoFragment);
}


navController.setGraph(graph);
}

activity_main.xml:

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


<!-- Following line omitted inside <fragment> -->
<!-- app:navGraph="@navigation/navigation_main" -->
<fragment
android:id="@+id/fragment_main_nav_host"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
/>


</androidx.constraintlayout.widget.ConstraintLayout>

navigation_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<!-- Following line omitted inside <navigation>-->
<!-- 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/oneFragment"
android:name="com.apponymous.apponymous.OneFragment"
android:label="fragment_one"
tools:layout="@layout/fragment_one"/>
<fragment
android:id="@+id/twoFragment"
android:name="com.apponymous.apponymous.TwoFragment"
android:label="fragment_two"
tools:layout="@layout/fragment_two"/>
</navigation>

This can be done with navigation action. Because fragmentA is your start destination, so define an action in fragmentA.

Note these two fields: app:popUpToInclusive="true" app:popUpTo="@id/fragmentA"

<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/lrf_navigation"
app:startDestination="@id/fragmentA">


<fragment
android:id="@+id/fragmentA"
android:name="com.mindinventory.FragmentA"
android:label="fragment_a"
tools:layout="@layout/fragment_a">


<action android:id="@+id/action_a_to_b"
app:destination="@id/fragmentB"
app:popUpToInclusive="true"
app:popUpTo="@id/fragmentA"/>
<fragment>


<fragment
android:id="@+id/fragmentB"
android:name="com.mindinventory.FragmentB"
android:label="fragment_b"
tools:layout="@layout/fragment_b"/>
</navigation>

When your MainActivity started, just do the navigation with action id, it will remove fragmentA in the stack, and jump to fragmentB. Seemingly, fragmentB is your start destination.

if(!isAllSetUp)
{
// FragmentB
navController.navigate(R.id.action_a_to_b)
}

You can set your starting destination programmatically, however, most of the time your starting logic will consult some remote endpoint. If you don't show anything on screen your UI will look bad.

What I do is always show a Splash screen. It will determine which is the next Screen to Show.

enter image description here

For instance, in the picture above you can ask in the Splash Screen State if there is a saved LoginToken. In case it's empty then you navigate to the Login Screen.

Once the Login Screen is done, then you analyze the result save the Token and navigate to your Next Fragment Home Screen.

When the Back Button is Pressed in the Home Screen, you will send back a Result message to the Splash Screen that indicates it to finish the App.

To pop 1 Fragment back and navigate to another you can use the following code:

val nextDestination = if (loginSuccess) {
R.id.action_Dashboard
} else {
R.id.action_NotAuthorized
}


val options = NavOptions.Builder()
.setPopUpTo(R.id.loginParentFragment, true)
.build()


findNavController().navigate(nextDestination, null, options)

this is not an answer but Just a replication of @Akash Patel answer in more clean and clear way

// in your MainActivity
navController = findNavController(R.id.nav_host_fragment)
val graph = navController.navInflater.inflate(R.navigation.nav_graph)
if (Authentication.checkUserLoggedIn()) {
graph.startDestination = R.id.homeFragment
} else {
graph.startDestination = R.id.loginFragment
}
navController.graph = graph