Android Navigation Architecture Component

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"
}

(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.

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)

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.

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.