How can I use Navigation (NavHost) in nested fragments? - android

I'm trying to open Fragment2 from Fragment1(TransformFragment)
throught click item in RecyclerView. I tried to use Navigation (NavHost) to solve this problem.
Fragment1 code as below:
class TransformFragment : Fragment() {
private lateinit var transformViewModel: TransformViewModel
private var _binding: FragmentTransformBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding
get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
transformViewModel = ViewModelProvider(this)[TransformViewModel::class.java]
_binding = FragmentTransformBinding.inflate(inflater, container, false)
val root: View = binding.root
val recyclerView = binding.recyclerviewTransform
val adapter = TransformAdapter()
recyclerView.adapter = adapter
transformViewModel.texts.observe(viewLifecycleOwner) { adapter.submitList(it) }
return root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
class TransformAdapter() :
ListAdapter<String, TransformViewHolder>(
object : DiffUtil.ItemCallback<String>() {
override fun areItemsTheSame(oldItem: String, newItem: String): Boolean =
oldItem == newItem
override fun areContentsTheSame(oldItem: String, newItem: String): Boolean =
oldItem == newItem
}
) {
private val drawables =
listOf(
R.drawable.avatar_1,
R.drawable.avatar_2,
R.drawable.avatar_3,
R.drawable.avatar_4,
R.drawable.avatar_5,
R.drawable.avatar_6,
R.drawable.avatar_7,
R.drawable.avatar_8,
R.drawable.avatar_9,
R.drawable.avatar_10,
R.drawable.avatar_11,
R.drawable.avatar_12,
R.drawable.avatar_13,
R.drawable.avatar_14,
R.drawable.avatar_15,
R.drawable.avatar_16,
R.drawable.avatar_17,
)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransformViewHolder {
val binding = ItemTransformBinding.inflate(LayoutInflater.from(parent.context))
return TransformViewHolder(binding)
}
override fun onBindViewHolder(holder: TransformViewHolder, position: Int) {
holder.textView.text = getItem(position)
holder.imageView.setImageDrawable(
ResourcesCompat.getDrawable(holder.imageView.resources, drawables[position], null)
)
holder.itemView.setOnClickListener {
// my navigation(NavHost) code
}
}
}
class TransformViewHolder(binding: ItemTransformBinding) :
RecyclerView.ViewHolder(binding.root) {
val imageView: ImageView = binding.imageViewItemTransform
val textView: TextView = binding.textViewItemTransform
}
}
I tried to use this:
val navController = findNavController(R.id.nav_host_fragment_content_main)
navController.navigate(R.id.nav_detail)
nav_detail to navigate Fragment2, and My Fragment Container. This's my XML. I know that to display Dynamiclly Fragments, you must use FrameLayout, but it produces errors and the fragment container is OK.
<LinearLayout 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"
android:orientation="vertical"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:showIn="#layout/app_bar_main">
<fragment
android:id="#+id/nav_host_fragment_content_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginLeft="#dimen/fragment_horizontal_margin"
android:layout_marginRight="#dimen/fragment_horizontal_margin"
android:layout_weight="1"
app:defaultNavHost="true"
app:navGraph="#navigation/mobile_navigation" />
<!--
<FrameLayout
android:id="#+id/nav_host_fragment_content_main"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginLeft="#dimen/fragment_horizontal_margin"
android:layout_marginRight="#dimen/fragment_horizontal_margin"
android:layout_weight="1"
app:defaultNavHost="true"
app:navGraph="#navigation/mobile_navigation" />
-->
</LinearLayout>

I've found the correct answer in Android developers documentation.
holder.itemView.setOnClickListener {
it.findNavController().navigate(R.id.nav_detail)
}

To use findNavController() to navigate between fragments, you've to make sure that you've setup with activity properly first.
So, in your activity layout first add container, parent need nod to be FrameLayout itself, I'm gonna make use of ConstraintLayout below.
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">
<!-- We declare attribute id to hook with MainActivity.java class-->
<!-- Attribute name will change behavior of simple fragment to NavHostFragment-->
<!-- Keep width and height to match_parent so that all our fragments take whole space-->
<!-- We change this NavHost to behave default NavHost-->
<!-- Attach our app navigation graph to this NavHostFragment-->
<fragment
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
Now let's hook container as an acting host for activity.
MainActivity.kt
class MainActivity : AppCompatActivity() {
lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// NavigationController to set default NavHost as nav_host_fragment.
navController = Navigation.findNavController(this, R.id.nav_host_fragment)
NavigationUI.setupActionBarWithNavController(this, navController)
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp()
}
}
onSupportNavigateUp function helps you to navigate back to previous fragment on action trigered or called specifically on any view with navigateUp().
Now you want to navigate from Fragment1 -> Fragment2.
Make sure you've added two fragments to navigation graph.
Connect first fragment to second with action.
Keep track of destination Id's to call correct one.
<?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/nav_graph"
app:startDestination="#id/firstFragment">
<fragment
android:id="#+id/firstFragment"
android:name=".FirstFragment"
android:label="FirstFragment"
tools:layout="#layout/fragment_first">
<action
android:id="#+id/action_first_fragment_to_second_fragment"
app:destination="#id/secondFragment" />
</fragment>
<fragment
android:id="#+id/secondFragment"
android:name=".SecondFragment"
android:label="Authors"
tools:layout="#layout/fragment_second" />
</navigation>
Now finally once action is triggered call action with id like below on controller.
yourListItemView.setOnClickListener {
findNavController(R.id.action_first_fragment_to_second_fragment)
}
That's it.
Please check the documentation, it has a good information.

Related

BottomNavigationView reloading fragments

I have implemented BottomNavigationView with NavController but fragments keep reloading on each click, even with the new navigation library update. I also tried setOnNavigationItemReselectedListener but it doesn't trigger anything. What am I doing wrong ?
I know this question have been asked more than once but I feel like I tried them all and none of them work. I was hoping I could avoid reverting my code with old libraries or using boilerplate solutions.
Here is my code :
build.gradle (:app)
implementation 'androidx.navigation:navigation-runtime-ktx:2.5.2'
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.5.2'
HomeActivity.kt
class HomeActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener,
Serializable {
private lateinit var binding: ActivityHomeBinding
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding= ActivityHomeBinding.inflate(layoutInflater)
setContentView(binding.root)
val navHostFragment = supportFragmentManager.findFragmentById(R.id.home_container) as NavHostFragment
navController = navHostFragment.navController
binding.bottomNavigation.setupWithNavController(navController)
}
}
activity_home.xml
<RelativeLayout 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"
android:fitsSystemWindows="true"
tools:openDrawer="start"
tools:context=".HomeActivity">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/home_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="#+id/bottom_navigation"
app:layout_constraintTop_toBottomOf="#+id/appbar_layout"
app:navGraph="#navigation/nav_main" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="#menu/bottom_navigation_menu"
app:layout_constraintTop_toBottomOf="#+id/home_container"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.5"/>
</RelativeLayout>
nav_main.xml
<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/nav_main"
app:startDestination="#id/firstFragment">
<fragment
android:id="#+id/firstFragment"
android:name="com.projectName.ui.main.ContactsFragment"
tools:layout="#layout/fragment_contacts" />
<fragment
android:id="#+id/secondFragment"
android:name="com.projectName.SearchFragment"
android:label="#string/search"
tools:layout="#layout/search_fragment" />
<fragment
android:id="#+id/thirdFragment"
android:name="com.projectName.ui.main.NotificationsFragment"
tools:layout="#layout/notifications_fragment" />
<fragment
android:id="#+id/fourthFragment"
android:name="com.projectName.chat.ChatFragment"
tools:layout="#layout/fragment_main_page" />
</navigation>
bottom_navigation_menu.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/firstFragment"
android:enabled="true"
android:icon="#drawable/ic_baseline_contacts_24"
android:title=""
app:labelVisibilityMode="unlabeled"/>
<item
android:id="#+id/secondFragment"
android:enabled="true"
android:icon="#drawable/ic_search_white_24dp"
android:title=""
app:labelVisibilityMode="unlabeled"/>
<item
android:id="#+id/thirdFragment"
android:enabled="true"
android:icon="#drawable/ic_baseline_notifications_none_24"
android:title=""
app:labelVisibilityMode="unlabeled"/>
<item
android:id="#+id/fourthFragment"
android:enabled="true"
android:icon="#drawable/ic_message_white_24dp"
android:title=""
app:labelVisibilityMode="unlabeled"/>
</menu>
Fragments
class NotificationsFragment : Fragment() {
companion object {
fun newInstance() = NotificationsFragment()
}
private lateinit var viewModel: NotificationsViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(this).get(NotificationsViewModel::class.java)
return inflater.inflate(R.layout.notifications_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
I also gave every layout an ID like the document of savedInstateState suggests.
Thank you.
EDIT
I could not find a solution for my problem so I used BottomNavigationView with ViewPager2 :
HomePagerAdapter.kt
private val TAB_TITLES = arrayOf(
Constants.CONTACTS,
Constants.SEARCH,
Constants.NOTIFICATIONS,
Constants.CHATS
)
class HomePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
override fun getItemCount(): Int = TAB_TITLES.size
override fun createFragment(position: Int): Fragment {
return if (getPageTitle(position) == Constants.CONTACTS) {
ContactsFragment.newInstance()
} else if (getPageTitle(position) == Constants.SEARCH) {
SearchFragment.newInstance()
} else if (getPageTitle(position) == Constants.NOTIFICATIONS) {
NotificationsFragment.newInstance()
} else {
ChatsFragment.newInstance()
}
}
private fun getPageTitle(position: Int): String {
return TAB_TITLES[position]
}
}
HomeActivity.kt
class HomeActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener,
Serializable, SearchFragment.OnPersonSelectedListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
val homePagerAdapter = HomePagerAdapter(this)
val viewPager: ViewPager2 = findViewById(R.id.home_view_pager)
viewPager.adapter = homePagerAdapter
viewPager.isUserInputEnabled = false
viewPager.offscreenPageLimit = 1
viewPager.overScrollMode = ViewPager2.OVER_SCROLL_NEVER
findViewById<BottomNavigationView>(R.id.bottom_navigation)
.setOnItemSelectedListener {
when (it.itemId) {
R.id.firstFragment -> {
viewPager.setCurrentItem(0, false)
return#setOnItemSelectedListener true
}
R.id.secondFragment -> {
viewPager.setCurrentItem(1, false)
return#setOnItemSelectedListener true
}
R.id.thirdFragment -> {
viewPager.setCurrentItem(2, false)
return#setOnItemSelectedListener true
}
R.id.fourthFragment -> {
viewPager.setCurrentItem(3, false)
return#setOnItemSelectedListener true
}
else -> return#setOnItemSelectedListener false
}
}
}
}
activity_home.xml
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/home_view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/bottom_navigation"
app:layout_constraintTop_toBottomOf="#+id/appbar_layout"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="#menu/bottom_navigation_menu"
app:layout_constraintTop_toBottomOf="#+id/home_view_pager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.5"/>
If I understood your problem right, you want that whenever you change from one fragment to another by using the bottom navigation bar that the fragments will perserve their state. I think this might be helpful https://developer.android.com/guide/navigation/multi-back-stacks. Every tab on the bottom navigation will have its own graph and while switching from one tab to another the state will be saved. Let me know if this helps you out!

How to set startDestination in Navigation Component based on the fragment/nav_graph I come from?

I'm using Navigation Component with Kotlin, and have three main tabs in nav_dashboard - Explore, My Groups and Profile. When I start application the startDestination is nav_explore, but then I want to go from there to different screens eg. CreateGroupFragment/nav_create_group and from there I have button that I want to redirect me to nav_dashboard but with My Groups selected - I guess that then the startDestination needs to be nav_profile. But how I can I implement that?
Dashboard Fragment (it contains Fragment Container View in XML):
internal class DashboardFragment : Fragment() {
lateinit var navigator: DashboardNavigator
private var _binding: FragmentDashboardBinding? = null
private val binding
get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentDashboardBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initBottomNavigation()
}
private fun initBottomNavigation() {
with(binding.bottomNavigationView) {
setupWithNavController(getDashboardNestedNavController())
}
}
private fun getDashboardNestedNavController(): NavController {
val navHostFragment = childFragmentManager.findFragmentById(R.id.nav_host_container) as NavHostFragment
val navGraph = navHostFragment.navController.navInflater.inflate(R.navigation.nav_dashboard_menu)
navHostFragment.navController.addOnDestinationChangedListener { _, destination, bundle ->
when (destination.id) {
R.id.exploreFragment, R.id.myGroupsFragment, R.id.profileFragment -> setBottomNavVisibility(View.VISIBLE)
else -> setBottomNavVisibility(View.GONE)
}
}
return navHostFragment.navController
}
private fun setBottomNavVisibility(visibility: Int) {
binding.bottomNavigationView.visibility = visibility
}
}
nav_dashboard:
<?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"
android:id="#+id/nav_dashboard_menu"
app:startDestination="#id/nav_explore">
<include app:graph="#navigation/nav_explore" />
<include app:graph="#navigation/nav_my_groups" />
<include app:graph="#navigation/nav_profile" />
</navigation>
nav_create_group:
<?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/nav_create_group"
app:startDestination="#id/createGroupFragment">
<include app:graph="#navigation/nav_create_group_map" />
<include app:graph="#navigation/nav_my_groups" />
<fragment
android:id="#+id/createGroupFragment"
android:name="com.wojciechkula.locals.presentation.creategroup.CreateGroupFragment"
android:label="#string/create_group_create_group"
tools:layout="#layout/fragment_create_group">
<action
android:id="#+id/openMap"
app:destination="#id/nav_create_group_map" />
<!-- Here I want another action to navigate to nav_dashboard with startDestination: nav_my_groups-->
</fragment>
</navigation>
First of all, you don't need to change the startDestination.
Secondly, why are you using multiple graphs? That usually makes things more complicated!
Anyway, you can just set the selected tab manually.
binding.bottomNavigationView.menu.findItem(R.id.tabId).isChecked = true
"tabId" is the id of the item in your bottom nav xml file.

View binding of a frgament inside a constraint layout not working

I have the following code:
activity_reminders.xml
<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=".locationreminders.RemindersActivity">
<fragment
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
ReminderActivity.kt
class RemindersActivity : AppCompatActivity() {
private lateinit var binding: ActivityRemindersBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityRemindersBinding.inflate(layoutInflater)
setContentView(binding.root)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
(binding.navHostFragment as NavHostFragment).navController.popBackStack()
return true
}
}
return super.onOptionsItemSelected(item)
}
}
binding.navHostFragment in unresolved and i don't understand why, if i change the type of view in anything differnt then "fragment" the view binding works.
Is possible to use view vbinding for a fragment inside another view?
I found the following as a workaround solution to get the fragment.
val navHostFragment = findNavController(R.id.nav_host_fragment)
navHostFragment.popBackStack()
I think that is not possible to use view binding with a <fragment> because fragment is not a descendent of View.
Instead of:
(binding.navHostFragment as NavHostFragment).navController.popBackStack()
Use the following code:
(supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController.popBackStack()

Android: How to open a Fragment with Navigation Graph without replacing the current Fragment

I am wondering if there is a way to add another fragment to the screen without replacing the old one with the Navigation Graph.
I want to do this because the new fragment will just be open for a bit just to check some details and when the user returns to the previous fragment, I want them to be in the same spot they left off.
I have read around but I couldn't find one that does it without replacing the original. I have tried to just add it to the nav_graph_fragment which I try to do this in the newFrag() function in the CurrentFragment section but it doesn't work.
Main Activty
class MainActivity : AppCompatActivity() {
private lateinit var toolbar: Toolbar
private val navController by lazy {
(supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController
}
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
toolbar = findViewById(R.id.mainToolBar)
setSupportActionBar(toolbar)
appBarConfiguration = AppBarConfiguration(navController.graph, null)
setupActionBarWithNavController(navController, appBarConfiguration)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
val inflater: MenuInflater = menuInflater
inflater.inflate(R.menu.main_menu_bar, menu)
return true
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val navController = findNavController(R.id.nav_host_fragment)
if (item.itemId == navController.currentDestination?.id)
return true
return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item)
}
}
Main Activity xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.MainActivity">
<include
layout="#layout/appbar"
android:id="#+id/mainToolBar"/>
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#id/mainToolBar"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
CurrentFragment
class CurrentFragment : Fragment() {
private lateinit var currentFragmentViewModel: CurrentFragmentViewModel
private lateinit var binding: CurrentFragmentBinding
private lateinit var recyclerView: RecyclerView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.current_fragment, container, false)
currentFragmentViewModel = ViewModelProvider(this).get(CurrentFragmentViewModel::class.java)
binding.lifecycleOwner = this
binding.viewmodel = currentFragmentViewModel
binding.accountButton.setOnClickListener {
Log.d("TEST", "button clicked")
newFrag()
}
return binding.root
}
// Here is where I am trying to open the new fragment
private fun newFrag() {
val newFrag = Fragment()
val transaction: FragmentTransaction = parentFragmentManager.beginTransaction()
transaction.hide(this)
transaction.add(R.id.nav_host_fragment, newFrag).commit()
}
}
Nav Graph
<?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/nav_graph"
app:startDestination="#id/mainFragment">
<fragment
android:id="#+id/mainFragment"
android:name="com.example.app.ui.main.MainFragment"
android:label="MainFragment">
<action
android:id="#+id/action_mainFragment_to_currentFragment"
app:destination="#id/currentFragment" />
</fragment>
<fragment
android:id="#+id/currentFragment"
android:name="com.example.app.ui.search.CurrentFragment"
android:label="currentFragment" >
</fragment>

Android: Navigation Component - How to connect Toolbar menu to the fragments?

Iv been stuck in trying to add the Navigation Component to my app. Specifically to my toolbar menu items.
I have been following the navigation migrate documentation on Android's site but I am getting a but confused on how it works or even to set it up.
This is a single-activity multi-fragment architecture.
The app would start in the MainFragment and then when tapping the menu items in the toolbar, such as Main Frag to Search Movie Frag. The user would be able to navigate using the menu items from anywhere in the app (e.g. if they click the home icon, they should be sent to the home screen form anywhere in the app. Still thinking if thats how it should be done).
The main thing is I don't know how to properly attach the Navigation Component to the menu items.
App
Nav Graph
nav_graph.xml
<?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/nav_graph"
app:startDestination="#id/mainFragment">
<fragment
android:id="#+id/mainFragment"
android:name="com.example.movieapp.ui.main.MainFragment"
android:label="MainFragment">
<action
android:id="#+id/action_mainFragment_to_searchMovieFragment"
app:destination="#id/searchMovieFragment" />
</fragment>
<fragment
android:id="#+id/searchMovieFragment"
android:name="com.example.movieapp.ui.search.SearchMovieFragment"
android:label="SearchMovieFragment" />
</navigation>
main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.MainActivity">
<include
layout="#layout/appbar"
android:id="#+id/mainToolBar"/>
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#id/mainToolBar"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var toolbar: Toolbar
private val navController by lazy {
(supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController
}
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
toolbar = findViewById(R.id.mainToolBar)
setSupportActionBar(toolbar)
appBarConfiguration = AppBarConfiguration(navController.graph, null)
setupActionBarWithNavController(navController, appBarConfiguration)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
val inflater: MenuInflater = menuInflater
inflater.inflate(R.menu.main_menu_bar, menu)
return true
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
}
MainFragment.kt
class MainFragment : Fragment() {
companion object {
fun newInstance() = MainFragment()
}
private lateinit var viewModel: MainViewModel
private lateinit var binding: MainFragmentBinding
private lateinit var navController: NavController
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = DataBindingUtil.inflate(inflater, R.layout.main_fragment, container, false)
setHasOptionsMenu(true)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
navController = Navigation.findNavController(view)
// navController.navigate(R.id.action_mainFragment_to_searchMovieFragment)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
binding.viewModel = viewModel
// TODO: Use the ViewModel
}
}
main_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.example.movieapp.ui.main.MainViewModel" />
</data>
<LinearLayout
android:orientation="vertical"
android:id="#+id/main_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.main.MainFragment">
<TextView
android:id="#+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MainFragment1" />
</LinearLayout>
</layout>
The id for a menu item in your file main_menu_bar.xml needs to match the id for the destination(fragment) specified in your navigation graph (nav_graph.xml).
Your main_menu_bar.xml should look something like this:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group android:checkableBehavior="single">
<item
android:id="#id/searchMovieFragment"
android:icon="#drawable/search"
android:title="#string/menu_search" />
<item
android:id="#id/mainFragment"
android:icon="#drawable/ic_menu_camera"
android:title="#string/menu_home" />
</group>
</menu>
Also, you need to override the onOptionsItemSelected(MenuItem) method and associate your menu items with destinations. Like so:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val navController = findNavController(R.id.nav_host_fragment)
return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item)
}
Reference
You can add a Toolbar on top of the most basic FragmentContainerView, access this toolbar from other fragments, and change the toolbar I created separately according to the fragment in it while opening the relevant fragment as follows.
toolbar = requireActivity().findViewById(R.id.toolbar_base)
toolbar?.menu?.clear()
toolbar?.title = ""
val toolbarFlowFragment = context?.let { getInflateLayout(it, R.layout.toolbar_profile) }
toolbar?.addView(toolbarFlowFragment)

Categories

Resources