The new trend in android dev is single activity, multiple fragments model. When we navigate, we usually create a single empty activity which acts as a basement. All other screens of the app will be created using fragments. This is the recommended best practice by Google android team.
There are three main parts of a navigation component.
- Navigation graph - XML resource file that contains all navigation-related information. This allows us to manage navigation related tasks from a single location.
- NavHostFragment - Empty container we keep on the activity to hold the navigation graph.
- NavController - Class generated by the navigation library to manage the navigation between destinations we added to the navigation graph.
Dependencies
Add navigation dependencies, safeargs and data binding.
apply plugin: "androidx.navigation.safeargs.kotlin"
android {
dataBinding {
enabled = true
}
}
dependencies {
def nav_version = "2.1.0"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}
Remember to add <layout>
tags to the outer most tag in .xml
files.
Add this classpath in your top level build.gradle
.
dependencies {
def nav_version = "2.1.0"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
Navigation Graph
(add photo of nav_graph)
It shows all the App’s screens and how the user can reach them, in a single location. They’re called destinations. It allows us to manage all the navigated related actions.
To create it, right click in the app folder > new > android resource file > file name: nav_graph; resource type: navigation
. It will appear under a new folder navigation
When created, there’s no destination nor NavHostFragments yet.
NavHostFragment
To host the navigation graph, we need a host fragment. Navigation graph connects to other parts of the app through this host fragment.
To add a NavHostFragment, go to activity_main.xml
, designer mode, select Containers, drag NavHostFragment. It automatically recognizes the navigation graph we created previously. Click on it. Click on infer constraints.
(add photo NavHostFragment1)
Under the hood, this is just a newly added fragment, with some special properties.
Now it should show the host on nav_graph.xml
.
(add photo NavHostFragment2)
Navigation destinations
Different fragments in the nav graph are called navigation destinations. Click on *add a destination* > Create new destination > Fragment Name: HomeFragment > Accept
.
This will:
- Create a new
HomeFragment.kt
class. - Create a new
fragment_home.xml
layout resource. - Add it to
nav_graph.xml
- Add a placeholder to
strings.xml
Inside nav_graph.xml
it will contain a home symbol. This indicates start destination. If you doule click on the fragment’s representation, this takes you to its fragment_home.xml
. Since ConstraintLayout
is much easier to work with, replace the main Container with it. right click on FrameLayout > Convert FrameLayout to ConstraintLayout > OK
. If you want to use DataBinding, remember to surround it by <layout>
tags.
Add a new destination
In nav_graph.xml
select the graph you want to be the base. Click on New Destination > Create new Destination > Fragment Name: SecondFragment; unclick options > OK
.
Now we have two destinations.
Navigate between screens (Actions)
We have two empty fragments: HomeFragment
and SecondFragment
. Go to nav_graph.xml
. Select HomeFragment
, hover the mouse over it and drag the circle it will appear to its SecondFragment
. This creates an action with a given id action_homeFragment_to_secondFragment
.
To execute this action on the click of a button, go to your HomeFragment
(using DataBinding) and set the onClickListener
action.
class HomeFragment: Fragment() {
private lateinit var binding: FragmentHomeBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_home, container, false)
// this here
binding.button.setOnClickListener {
it.findNavController().navigate(R.id.action_homeFragment_to_secondFragment)
}
return binding.root
}
}
Transfer data between destinations
It’s not recommended to pass data beween destinations. The best practice is using a view model and get the data from the view model, but Android Navigation architecture component allows us to pass data.
In the HomeFragment
we have an EditText
with id editText
to get the user input and a submit Button
. In the SecondFragment
we have a TextView
. We will display the user input from HomeFragment
into SecondFragment
.
Starting point for HomeFragment
.
class HomeFragment: Fragment() {
private lateinit var binding: FragmentHomeBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_home, container, false)
binding.button.setOnClickListener {
it.findNavController().navigate(R.id.action_homeFragment_to_secondFragment)
}
return binding.root
}
}
We get the user input as a bundle. Then we pass tha bundle as the second argument of this navigate function.
class HomeFragment: Fragment() {
private lateinit var binding: FragmentHomeBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_home, container, false)
binding.button.setOnClickListener {
// this here. The if avoids possible null pointers.
if(!TextUtils.isEmpty(binding.editText.text.toString())) {
val bundle = bundleOf("user_input" to binding.editText.text.toString())
it.findNavController().navigate(R.id.action_homeFragment_to_secondFragment, bundle)
}
}
return binding.root
}
}
Starting point for SecondFragment
.
class SecondFragment: Fragment() {
private lateini var binding: FragmentSecondBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?.
savedInstanceState: Bundle?
): View? {
// inflate the layout for this fragment
binding = DataBindingUtil.inflate(inflate, R.layout.fragment_second, conainer, false)
return binding.root
}
}
We have added the bundle as an argument, so here we need to retrieve it.
class SecondFragment: Fragment() {
private lateini var binding: FragmentSecondBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?.
savedInstanceState: Bundle?
): View? {
// inflate the layout for this fragment
binding = DataBindingUtil.inflate(inflate, R.layout.fragment_second, conainer, false)
// this here
var input: String? = arguments!!.getString("user_input" )
binding.textView.ext = input.toString()
return binding.root
}
}
## Animations for Actions
Go to navigation_graph.xml
and select an action action_homeFragment_to_secondFragment
. There’s a block Animations where you may select animations for Enter
, Exit
, Pop Enter
, Pop Exit
.
When we navigate from one destination to anoher, the new screen will appear with Enter
animation, and the previous window will disspear with Exit
animation.
When the user clicks on the back button of he phone (the screen navigates backwards), the current screen will disappear with Pop Exit
and the previous window will appear with the Pop Enter
animation.
You may search for animations, such as this here.
Take one of them and create a resource file Right click on project > new > Android Resource File > File name: slide_in_left; Resource Type: Animation
. Copy and paste the animation you want.
With the animations already created, select the one you want for the action.