Introduction
A lot has changed over the last couple of years when it comes to our development approach in Android. We have a simpler dependency framework in the form of Dagger-Hilt, AndroidX libraries which have improved the use of many android components, co-routines which have saved us from callback hell, a much improved ORM library in Room and yes, a much, much better navigation system. One common issue developers face is the 'unable to start activity' error, which typically arises when the application fails to set a NavController for the activity. Without wasting too much of your time, let’s get into how we can start another fragment for results.
Understanding the Issue
When working with the Android Navigation Component, you might encounter an IllegalStateException while trying to navigate to a fragment. This error typically occurs because the fragment does not have a NavController set on it. The NavController is a critical part of the Navigation Component, responsible for managing navigation between fragments. Without a NavController, the Navigation Component cannot perform the navigation action, leading to the error.
Fragment and NavController Relationship
For the Navigation Component to function correctly, each fragment must have a NavController set. This is usually achieved by adding the fragment to a NavHostFragment, a special type of fragment designed to host a navigation graph. When a fragment is part of a NavHostFragment, it automatically gains access to the NavController instance, enabling smooth navigation.
However, if a fragment is not added to a NavHostFragment, it will not have a NavController set. This absence of a NavController is what triggers the IllegalStateException, as the Navigation Component cannot find the necessary NavController instance to navigate to the intended destination.
Let's Dive In to start activity componentinfo
Suppose you want to move from Fragment A to Fragment B and you’d like to get some results back from Fragment B when returning to Fragment A, you’ll set up Fragment A like this: When I was working on a similar project, I encountered an issue with passing data back to the previous fragment. class FragmentA : Fragment override fun onViewCreatedview: View, savedInstanceState: Bundle? super.onViewCreatedview, savedInstanceState
class FragmentA : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Observe the result to be set by Fragment B in the stateHandle of the currentBackStackEntry
val currentBackStackEntry = findNavController().currentBackStackEntry
val savedStateHandle = currentBackStackEntry?.savedStateHandle
savedStateHandle?.getLiveData<String>(RESULT_FROM_FRAGMENT_B)
?.observe(currentBackStackEntry, Observer { result ->
print(result)
})
}
private fun navigateToFragmentB() {
val navDirection = FragmentADirection.actionFragmentAToFragmentB()
findNavController().navigate(navDirection)
}
companion object {
const val RESULT_FROM_FRAGMENT_B = "RESULT_FROM_FRAGMENT_B"
}
}
Then you'll set up Fragment B to set the result that is being observed by Fragment A
class FragmentB : Fragment() {
private fun navigateBackToFragmentA() {
//Setting the result in the stateHandle of the previousBackStackEntry before navigating back to Fragment A
//will allow Fragment A to access the result in the stateHandle of its currentBackStackEntry
val savedStateHandle = findNavController().previousBackStackEntry?.savedStateHandle
savedStateHandle?.set(FragmentA.RESULT_FROM_FRAGMENT_B, "result")
findNavController().navigateUp()
}
}
That's it! You can easily navigate to any fragment and get the result by setting the stateHandle of the previousBackStackEntry and observing the result in the stateHandle of the currentBackStackEntry.
Best Practices
To prevent the IllegalStateException and ensure smooth navigation between fragments, consider the following best practices:
-
Use FragmentContainerView: Instead of using a FrameLayout in your layout file, opt for a FragmentContainerView. This view is specifically designed to hold a NavHostFragment and makes it easier to set the NavController instance.
-
Instantiate NavController in onPostCreate(): In your MainActivity, use the onPostCreate() method to instantiate the NavController instance. This method is called after the activity’s view has been created, ensuring that the NavController is available when needed.
-
Extend AppCompatActivity: Make sure your activity extends AppCompatActivity. This is essential for the Navigation Component to function correctly.
-
Correct Imports in Android Manifest: Ensure that your Android Manifest file includes the necessary permissions, activities, services, receivers, and providers. This helps in avoiding any runtime issues related to navigation.
By following these best practices, you can avoid the IllegalStateException and ensure that your app navigates smoothly between fragments using the Navigation Component.
My question is if we are observing a String in Fragment A, then why we pass a Boolean in fragment B?
// Navigation
implementation "android.arch.navigation:navigation-fragment-ktx:$version_navigation"
implementation "android.arch.navigation:navigation-ui-ktx:$version_navigation"
I cannot access currentBackStackEntry using: navController.currentBackStackEntry... Won't compile. Anything I am missing here?