Navigation is an important part of Android Jetpack, with Single Activity and multiple fragments, Optimize the startup overhead of Android activities and simplify data communication between activities.
Navigation is a complete set of Navigation framework, built-in support for normal Fragment, Activity and DialogFragment components jump, In other words, DialogFragment is recommended for all Dialog or PopupWindow implementations, which can cover all common jump scenarios and unify the management of the return stack. In addition, fragment-based implementations can store and restore state.
If you are a traditional Activity-based developer and want to migrate to the Navigation architecture, you must have the following questions:
- Fragment it all? How to support the original launch types of Activity jumps (singleTask, singleTop)?
- How do fragments communicate with each other?
- How should onActivityForResult implement an “onFragmentResult”?
- My project has been developed with Activity, how do I migrate to Navigation? What are the migration costs? Can Acitivity and Navigation be mixed?
Below, we start from the use of Navigation and answer the above questions one by one.
Creating a navigation chart
Introduction of depend on
implementation "androidx.navigation:navigation-fragment-ktx:$versions.nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$versions.nav_version"
Copy the code
The latest stable version is 2.3.5. For other historical versions, please check Android Navigation Releate Note.
Create a navigation diagram resource
- Create a new navigation directory under the RES directory and then create a new Navigation resource file, let’s say home.xml.
Create fragments
This assumes that there are welcome pages (Title) and About pages. Simply display text a paragraph of text, no longer post code.
Add a destination in the navigation diagram and set the starting destination.
Use Navigation graphical tool Design to quickly develop a Navigation diagram that supports filtering by component name.
We can set the preview layout of the destination to make the navigation map look more intuitive. After adding the Title and About destinations, the preview looks like this:
Let’s switch to “Code” mode and see what’s generated:
By default, the first component added to the navigation diagram is considered the starting destination, which can also be specified by modifying the app:startDestination of the navigation root node. When a navigation points to the Id of the navigation diagram itself, it navigates directly to the starting destination.
Organizational jump relation
Finally, we want to jump to the About page when we click a button on the Title page.
At this point, start from the Title page under Navigation Design panel and lead a line pointing to the About page to add a jump path.
You can see that you can set the transition animation and startup type for this jump.
Let’s switch to “Code” mode again and see what happens.
As you can see, the Action tag is automatically generated under the Title Fragment tag, and its destination attribute points to the About page, indicating that the destination is the About page.
Create a layout file
Next, we create the layout file for the main Activity, mainly by inserting the navigation graph inflate created in the first step.
<! -- main_activity.xml --> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <! - fragments containers - > < androidx. Fragments. App. FragmentContainerView android: id = "@ + id/nav_host_container" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" app:defaultNavHost="true" app:navGraph="@navigation/home" /> <! - at the bottom of the navigation - > < com. Google. Android. Material. Bottomnavigation. BottomNavigationView android: id = "@ + id/bottom_nav" android:layout_width="match_parent" android:layout_height="wrap_content" app:menu="@menu/bottom_nav"/> </LinearLayout>Copy the code
NavHostFragment
FragmentContainerView as a Fragment container, actual inflate in fragments to androidx navigation. Fragments. NavHostFragment.
NavHostFragment is a fragment that implements navigation, navigation, navigation, navigation, navigation, navigation, navigation, and navigation. That is, when the navigation graph is completed, the starting destination is displayed on the page.
A NavHostFragment corresponds to a NavController. The process of obtaining a NavController object is closely related to this.
Then we create one and only Activity, no different from a normal Activity.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
}
}
Copy the code
App: defaultNavHost properties
If the value is set to true, the navigation chart gives priority to the system return key. When the user returns by gesture or virtual return key, the Fragment return stack will be displayed first. If set to false, the Activity will exit directly.
There is only one place in the world where this property can be set to true.
3. Obtain the navigation controller to realize page hopping
At this point, we need to add a jump event to the Title page.
class Title : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup? , savedInstanceState: Bundle? ) : View? { val view = inflater.inflate(R.layout.fragment_title, container, False) view.findViewById<Button>(R.ida. bout_btn).setonClickListener { FindNavController ().navigate(r.i.a.ction_titlescreen_to_aboutscreen)} return view}} findNavController().navigate(r.I.a.ction_titlescreen_to_aboutscreen)Copy the code
Finally, look at the jump effect.
There’s a key API here: findNavController(), an extension of the Fragment method that returns a NavController object and gives the final jump by calling its navigate method. Let’s focus on how NavController is acquired and what it provides.
4. NavController acquisition and its capabilities
NavController access
In the example above, we can retrieve the NavController belonging to the Fragment by extending the Fragment method. There are also some overloaded methods:
FindNavController (Activity Activity, Int viewId) // findNavController(view view)Copy the code
FindNavController is essentially a view tree that finds the parent NavHostFragment of the specified view.
The ability of NavController
For the application layer, the whole Navigation framework, we only deal with the NavController, it provides the usual jump, return and return stack capabilities.
- Jump: The jump can be completed with various overloaded navigate methods, which can set the jump animation, jump parameters, launch mode, etc.
- Returns: The main difference between the popBackStack and navigateUp methods is that the navigateUp method does not exit the Activity when there is only one page left on the return stack (except in the Deeplink scenario, Discussed separately later), and popBackStack immediately exits the Activity; In other scenarios, the logic is the same.
- Get the return stack: Get the entire return stack using the getBackStackEntry method. GetCurrentBackStackEntry gets the return stack of the current page. The return stack can be used for page recovery and reconstruction, and NavBackStackEntry has lifecycle awareness and ViewModelStoreOwner, so it can be used for ViewModel data communication at the navigational graph level (more on Navigation principles here).
- Listening page jump change: use addOnDestinationChangedListener interface can be change in perception at any page in the navigation map, it is very practical, such as navigation selected state and content area at the bottom of the linkage.
4. Pass parameters when jumping
Parameters are passed through the navigate method with the bundle parameter
You can pass parameters to the destination by specifying the bundle parameter, for example:
findNavController().navigate(R.id.action_to_aboutScreen, bundleOf("key" to "title"))
Copy the code
You can get this bundle directly at the destination Fragment using the getArguments() method.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val value = arguments? .getString("key") ... }Copy the code
Through the safeArgs plugin
It can be found that in traditional jump mode, the name and type of the key must be specified on both sides of the start destination; otherwise, unknown errors may occur.
Therefore, the Navigation framework provides a separate safeArgs plugin to declare XML configuration files and automatically generate a wrapper class for all parameters in the compiler to help developers quickly encapsulate and unencapsulate jump parameters.
Suppose we now want to jump to About while passing a String argument:
- Add the safeArgs plug-in to the project root
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$versions.nav_version"
Copy the code
- Add safeArgs plugin to main project build.gradle
apply plugin: "androidx.navigation.safeargs"
Copy the code
- Add jump parameters for actions and destinations in the navigation diagram.
<? The 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/home" app:startDestination="@+id/titleScreen"> <fragment android:id="@+id/titleScreen" android:name=".Title" android:label="@string/title_home" tools:layout="@layout/fragment_title"> <! <action Android :id="@+id/action_titleScreen_to_aboutScreen" app:destination="@id/aboutScreen"> <argument android:name="key" app:argType="string"/> </action> </fragment> <fragment android:id="@+id/aboutScreen" android:name=".About" android:label="@string/title_about" tools:layout="@layout/fragment_about"> <! Android :name="key" app:argType="string"/> </fragment> </navigation>Copy the code
- The rebuild project will generate two classes, TitleDirections and AboutArgs in the form of XxxDirections and YyyyArgs. XXX is the id suffix of the starting Action and the name suffix of the ending fragment respectively. In this case, the jump code is changed to:
/ / Title starting point jump findNavController (). Navigate (TitleDirections. ActionTitleScreenToAboutScreen (" value ")) / / About the finish class About : Fragment() { // saftArgs API private val args by navArgs<AboutArgs>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val key = args.key ... }}Copy the code
Answer the opening question
All right, let’s go back to the opening question.
1. How to support the startup types of Fragment jumps (singleTask and singleTop)?
For singleTop support, the action TAB has an app:launchSingleTop property, which when set to true is the same as the Activity’s singleTop launch mode. That is, no new destination is recreated when the top of the stack is returned the same as the destination.
The Fragment does not receive an onNewIntent callback to update its jump parameters. Instead, it needs to add an onDestinationChanged listener to the Fragment. And use the new jump parameter (if necessary).
findNavController().addOnDestinationChangedListener {
controller, destination, arguments ->
...
}
Copy the code
There is no direct support for singleTask Navigation, replaced by the more general popup configuration.
The Action tag has two popup properties that act like a singleTask.
<action
android:id="@+id/action_to_title"
app:destination="@id/titleScreen"
app:popUpTo="@id/titleScreen"
app:popUpToInclusive="true"
/>
Copy the code
- PopUpTo: Specifies the destination to pop before performing the jump action.
- PopUpToInclusive: Whether the above pop action contains the destination given by popUpTo. Default is false.
For example, if the current return stack is A -> B -> C, C adds an action and specifies destination as A, popUpTo as A, popUpToInclusive as true, The return stack after the jump is A’ (because the old A has been removed from the return stack); Conversely, if popUpToInclusive is specified as false, the current return stack is A -> A’.
Therefore, it can be found that the singleTask mode implemented by Navigation is somewhat different from the Activity itself.
If you are sure that A is already in the return stack, you can also use popBackStack to return to A.
findNavController().popBackStack(R.id.A, inclusive = false)
Copy the code
2. How to communicate between fragments?
Communication in fragments can also be divided into two scenarios, assuming that there are currently two fragments A and B in the return stack.
- If A and B are in the same subgraph, they can interact at both ends by creating navigation-level viewModels.
For example, the current return stack is NavGraphA -> NavDestinationB -> NavDestinationC -> NavDestinationD
If you want to realize the communication between C and D, you need to create a ViewModel using node B.
val vm by navGraphViewModels<TitleVm>(R.id.nav_destination_b)
Copy the code
R.id.home is the nearest public parent Graph of the two, and the communication between the two is valid until the parent Graph is destroyed.
- If A and B are not in the same child Graph, they can communicate with each other using the nearest public parent Graph.
For example, the current return stack is NavGraphA -> NavDestinationB -> NavGraphC -> NavDestinationD
If you want to realize the communication between B and D, you need to create A ViewModel using node A.
val vm by navGraphViewModels<TitleVm>(R.id.home)
Copy the code
Finally, of course, you can communicate directly with the Activity-level ViewModel, but the ViewModel lifetime will be longer, and you should choose the ViewModel scope with the shortest lifetime depending on the situation.
3. My project has been developed with Activity, how do I migrate it to Navigation? Can Acitivity and Navigation be mixed?
To answer this question, you need to know how Navigation manages the return stack. This article will not go into the details, but only give the conclusion, which will be explained in detail in the principles section.
Mixed development is possible, but you need to make sure that there are no mixed jumps. Assuming FragmentA and FragmentC are in the same navigation diagram, the following jumps are not supported: FragmentA -> ActivityB -> Framgment C. Therefore, if conditions permit, completely independent deep secondary pages can be migrated with Navigation with low priority.
This is the same reason that Flutter does not support mixed stack development well.
4. How should “onFragmentResult” be implemented instead of onActivityForResult?
This problem can be implemented using the Fragment communication mechanism in Question 2, but we need to create a new ViewModel to hold the Result. There is a more elegant way: This is accomplished by SaveStateHandle, which will be explained in the next section, Principles of Naviagtion.
About me
- The Denver nuggets
- The public wanderingTech no.