Stop fragment refresh in bottom nav using navhost - android
This problem has been asked a few times now, but we are in 2020 now, did anyone find a good usable solution to this yet?
I want to be able to navigate using the bottom navigation control without refreshing the fragment each time they are selected. Here is what I have currently:
navigation/main.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/main"
app:startDestination="#id/home">
<fragment
android:id="#+id/home"
android:name="com.org.ftech.fragment.HomeFragment"
android:label="#string/app_name"
tools:layout="#layout/fragment_home" />
<fragment
android:id="#+id/news"
android:name="com.org.ftech.fragment.NewsFragment"
android:label="News"
tools:layout="#layout/fragment_news"/>
<fragment
android:id="#+id/markets"
android:name="com.org.ftech.fragment.MarketsFragment"
android:label="Markets"
tools:layout="#layout/fragment_markets"/>
<fragment
android:id="#+id/explore"
android:name="com.org.ftech.ExploreFragment"
android:label="Explore"
tools:layout="#layout/fragment_explore"/>
</navigation>
activity_mail.xml:
<?xml version="1.0" encoding="utf-8"?>
<!-- Use DrawerLayout as root container for activity -->
<androidx.drawerlayout.widget.DrawerLayout
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/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="#+id/bottomNavigationView"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/main" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemIconTint="#color/nav"
app:itemTextColor="#color/nav"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:menu="#menu/main">
</com.google.android.material.bottomnavigation.BottomNavigationView>
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.navigation.NavigationView
app:menu="#menu/main"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="#+id/navigationView"
android:layout_gravity="start">
</com.google.android.material.navigation.NavigationView>
</androidx.drawerlayout.widget.DrawerLayout>
MainActivity.kt:
class MainActivity : AppCompatActivity() {
private var drawerLayout: DrawerLayout? = null
private var navigationView: NavigationView? = null
private var bottomNavigationView: BottomNavigationView? = null
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
drawerLayout = findViewById(R.id.drawer_layout)
navigationView = findViewById(R.id.navigationView)
bottomNavigationView = findViewById(R.id.bottomNavigationView)
val navController = findNavController(R.id.nav_host_fragment)
appBarConfiguration = AppBarConfiguration(setOf(R.id.markets, R.id.explore, R.id.news, R.id.home), drawerLayout)
setupActionBarWithNavController(navController, appBarConfiguration)
findViewById<NavigationView>(R.id.navigationView)
.setupWithNavController(navController)
findViewById<BottomNavigationView>(R.id.bottomNavigationView)
.setupWithNavController(navController)
}
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment)
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.search) {
startActivity(Intent(applicationContext, SearchableActivity::class.java))
}
return super.onOptionsItemSelected(item)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.options_menu, menu)
return super.onCreateOptionsMenu(menu)
}
}
In the fragment I am making a few calls to my services to fetch the data in onCreateView, when resuming the fragment I am assuming those calls will not longer be executed and the state of the fragment should be preserved.
Try this:
public class MainActivity extends AppCompatActivity {
final Fragment fragment1 = new HomeFragment();
final Fragment fragment2 = new DashboardFragment();
final Fragment fragment3 = new NotificationsFragment();
final FragmentManager fm = getSupportFragmentManager();
Fragment active = fragment1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
fm.beginTransaction().add(R.id.main_container, fragment3, "3").hide(fragment3).commit();
fm.beginTransaction().add(R.id.main_container, fragment2, "2").hide(fragment2).commit();
fm.beginTransaction().add(R.id.main_container,fragment1, "1").commit();
}
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_home:
fm.beginTransaction().hide(active).show(fragment1).commit();
active = fragment1;
return true;
case R.id.navigation_dashboard:
fm.beginTransaction().hide(active).show(fragment2).commit();
active = fragment2;
return true;
case R.id.navigation_notifications:
fm.beginTransaction().hide(active).show(fragment3).commit();
active = fragment3;
return true;
}
return false;
}
};
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_menu, menu);
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
startActivity(new Intent(MainActivity.this, SettingsActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
}
Or You can follow Google's recommended solution: Google Link
The simple solution to stop refreshing on multiple clicks on the same navigation item could be
binding.navView.setOnNavigationItemSelectedListener { item ->
if(item.itemId != binding.navView.selectedItemId)
NavigationUI.onNavDestinationSelected(item, navController)
true
}
where binding.navView is the reference for BottomNavigationView using Android Data Binding.
Kotlin 2020 Google's Recommended Solution
Many of these solutions call the Fragment constructor in the Main Activity. However, following Google's recommended pattern, this is not needed.
Setup Navigation Graph Tabs
Firstly create a navigation graph xml for each of your tabs under the res/navigation directory.
Filename: tab0.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/tab0"
app:startDestination="#id/fragmentA"
tools:ignore="UnusedNavigation">
<fragment
android:id="#+id/fragmentA"
android:label="#string/fragment_A_title"
android:name="com.app.subdomain.fragA"
>
</fragment>
</navigation>
Repeat the above template for your other tabs. Important all fragments and the navigation graph has an id (e.g. #+id/tab0, #+id/fragmentA).
Setup Bottom Navigation View
Ensure the navigation ids are the same as the ones specified on the bottom menu xml.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="#string/fragment_A_title"
android:id="#+id/tab0"
android:icon="#drawable/ic_baseline_book_24"/>
<item android:title="#string/fragment_B_title"
android:id="#+id/tab1"
android:icon="#drawable/ic_baseline_add_alert_24"/>
<item android:title="#string/fragment_C_title"
android:id="#+id/tab2"
android:icon="#drawable/ic_baseline_book_24"/>
<item android:title="#string/fragment_D_title"
android:id="#+id/tab3"
android:icon="#drawable/ic_baseline_more_horiz_24"/>
</menu>
Setup Activity Main XML
Ensure FragmentContainerView is being used and not <fragment and do not set the app:navGraph attribute. This will set later in code
<androidx.fragment.app.FragmentContainerView
android:id="#+id/fragmentContainerView"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="#+id/bottomNavigationView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/main_toolbar"
/>
Main Activity XML
Copy over the following Code into your main activity Kotlin file and call setupBottomNavigationBar within OnCreateView. Ensure you navGraphIds use R.navigation.whatever and not R.id.whatever
private lateinit var currentNavController: LiveData<NavController>
private fun setupBottomNavigationBar() {
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottomNavigationView)
val navGraphIds = listOf(R.navigation.tab0, R.navigation.tab1, R.navigation.tab2, R.navigation.tab3)
val controller = bottomNavigationView.setupWithNavController(
navGraphIds = navGraphIds,
fragmentManager = supportFragmentManager,
containerId = R.id.fragmentContainerView,
intent = intent
)
controller.observe(this, { navController ->
val toolbar = findViewById<Toolbar>(R.id.main_toolbar)
val appBarConfiguration = AppBarConfiguration(navGraphIds.toSet())
NavigationUI.setupWithNavController(toolbar, navController, appBarConfiguration)
setSupportActionBar(toolbar)
})
currentNavController = controller
}
override fun onSupportNavigateUp(): Boolean {
return currentNavController?.value?.navigateUp() ?: false
}
Copy NavigationExtensions.kt File
Copy the following file to your codebase
[EDIT] The above link is broken. Found it in a forked repo
Source
Google's Solution
If you are using Jetpack, the easiest way to solve this is using ViewModel
You have to save all valuable data and not make unnecessary database loads or network calls everytime you go to a fragment from another.
UI controllers such as activities and fragments are primarily intended to display UI data, react to user actions, or handle operating system communication, such as permission requests.
Here is when we use ViewModels
ViewModel objects are automatically retained during configuration changes so that data they hold is immediately available to the next activity or fragment instance.
So if the fragment is recreated, all your data will be there instantly instead of make another call to database or network. Its important to know that if the activity or fragment that holds the ViewModel is reacreated, you will receive the same ViewModel instance created before.
But in this case you have to specify the ViewModel to have activity scope instead of fragment scope, independently if you are using a shared ViewModel for all the fragments, or a different ViewModel for every fragment.
Here is a little example using LiveData too:
//Using KTX
val model by activityViewModels<MyViewModel>()
model.getData().observe(viewLifecycleOwner, Observer<DataModel>{ data ->
// update UI
})
//Not using KTX
val model by lazy {ViewModelProvider(activity as ViewModelStoreOwner)[MyViewModel::class.java]}
model.getData().observe(viewLifecycleOwner, Observer<DataModel>{ data ->
// update UI
})
And that's it! Google is actively working on multiple back stack support for bottom tab Navigation and claim that it'll arrive on Navigation 2.4.0 as said here and on this issue tracker if you want and/or your problem is more related to multiple back stack, you can check out those links
Remember fragments still be recreated, usually you don't change component behavior, instead, you adapt your data to them!
I leave you some useful links:
ViewModel Overview Android Developers
How to communicate between fragments and activities with ViewModels - on Medium
Restoring UI State using ViewModels - on Medium
Quick tip, if you just want to prevent loading the already selected fragment just override setOnNavigationItemReselectedListener and do nothing, but this won't save the fragment states
binding.navBar.setOnNavigationItemReselectedListener { }
You use the old version, you just use version 2.4.0-alpha05 or above.
This answer is updated in 2021.
androidx.navigation:navigation-runtime-ktx:2.4.0-alpha05
androidx.navigation:navigation-fragment-ktx:2.4.0-alpha05
androidx.navigation:navigation-ui-ktx:2.4.0-alpha05
If you use NavigationUI.setupWithNavController(), the NavOptions are defined for you with NavigationUI.onNavDestinationSelected(). These options include launchSingleTop and, if the menu item is not secondary, a popUpTo the root of the graph.
The problem is, that launchSingleTop still replaces the top fragment with a new one. To resolve this issue, you'd have to create your own setupWithNavController() and onNavDestinationSelected() functions. In onNavDestinationSelected() you'd just adapt the NavOptions to your needs.
If you are using navigation component,In addition to this answer From version:'2.4.0-alpha01' it has inbuilt support for multiple back stacks.So no navigation extension is needed
refer to this link for more details. https://medium.com/androiddevelopers/navigation-multiple-back-stacks-6c67ba41952f
Try something like this
navView.setOnNavigationItemSelectedListener(onNavigationItemSelectedListener)
And
private val onNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.home -> {
fragmentManager.beginTransaction().hide(active).show(homeFragment).commit()
active = homeFragment
return#OnNavigationItemSelectedListener true
}
R.id.news -> {
fragmentManager.beginTransaction().hide(active).show(newsFragment).commit()
active = newsFragment
return#OnNavigationItemSelectedListener true
}
R.id.markets -> {
fragmentManager.beginTransaction().hide(active).show(marketsFragment).commit()
active = marketsFragment
return#OnNavigationItemSelectedListener true
}
R.id.explore -> {
fragmentManager.beginTransaction().hide(active).show(exploreFragment).commit()
active = exploreFragment
return#OnNavigationItemSelectedListener true
}
}
false
}
create a class:
#Navigator.Name("keep_state_fragment") // `keep_state_fragment` is used in navigation xml
class KeepStateNavigator(
private val context: Context,
private val manager: FragmentManager, // Should pass childFragmentManager.
private val containerId: Int
) : FragmentNavigator(context, manager, containerId) {
override fun navigate(
destination: Destination,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
): NavDestination? {
val tag = destination.id.toString()
val transaction = manager.beginTransaction()
var initialNavigate = false
val currentFragment = manager.primaryNavigationFragment
if (currentFragment != null) {
transaction.detach(currentFragment)
} else {
initialNavigate = true
}
var fragment = manager.findFragmentByTag(tag)
if (fragment == null) {
val className = destination.className
fragment = manager.fragmentFactory.instantiate(context.classLoader, className)
transaction.add(containerId, fragment, tag)
} else {
transaction.attach(fragment)
}
transaction.setPrimaryNavigationFragment(fragment)
transaction.setReorderingAllowed(true)
transaction.commitNow()
return if (initialNavigate) {
destination
} else {
null
}
}
}
Use keep_state_fragment instead of fragment in nav_graph
In Activity:
val navController = findNavController(R.id.nav_host_fragment)
// get fragment
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)!!
// setup custom navigator
val navigator = KeepStateNavigator(this, navHostFragment.childFragmentManager, R.id.nav_host_fragment)
navController.navigatorProvider += navigator
// set navigation graph
navController.setGraph(R.navigation.nav_graph)
bottom_navigation.setupWithNavController(navController)
Use this snippet:
private fun attachFragment(fragmentTag: String) {
val fragmentTransaction = supportFragmentManager.beginTransaction()
supportFragmentManager.findFragmentByTag(fragmentTag)?.let {
if (supportFragmentManager.backStackEntryCount == 0) return
val currentFragmentTag = supportFragmentManager.getBackStackEntryAt(supportFragmentManager.backStackEntryCount - 1).name
(supportFragmentManager.findFragmentByTag(currentFragmentTag) as? FragmentBase)?.let { curFrag ->
fragmentTransaction.hide(curFrag)
}
fragmentTransaction.show(it)
} ?: run {
when (fragmentTag) {
FragmentHome.TAG -> FragmentBase.newInstance<FragmentHome>()
FragmentAccount.TAG -> FragmentBase.newInstance<FragmentAccount>()
else -> null
}?.let {
fragmentTransaction.add(R.id.container, it, fragmentTag)
fragmentTransaction.addToBackStack(fragmentTag)
}
}
fragmentTransaction.commit()
}
You can use this pass the tag of specific fragment that you want to show now, using method attachFragment(FragmentHome.TAG)
Hi friend, it's new solution:
BottomNavigationView navView = findViewById(R.id.nav_view);
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main);
binding.navView.setOnItemSelectedListener(new NavigationBarView.OnItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
if (item.getItemId() != binding.navView.getSelectedItemId())
NavigationUI.onNavDestinationSelected(item, navController);
return true;
}
});
I was looking for the best way to handle this and finally i came out with this simple idea : deactivate the MenuItem currently selected.
This way, you cannot click twice on it and therefore reloading the fragment is prevented.
Don't forget to Enable it back when you go to another fragment through your navHost.
The mechanic is based on the NavHostFragment which receives your BottomNavigationView from within a fragment/activity.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
navHostFragment =
childFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navView: BottomNavigationView = view.findViewById(R.id.nav_view)
val navController = navHostFragment.navController
//Here you link the NavHostFragment's navController to your
//bottomMenu
navView.setupWithNavController(navController)
//Add a listener monitoring the destination changes
navController.addOnDestinationChangedListener(object : NavController.OnDestinationChangedListener{
override fun onDestinationChanged(
controller: NavController,
destination: NavDestination,
arguments: Bundle?
) {
/* Disable the selected item and re-enable the others */
for( item in navView.menu.iterator()){
item.isEnabled = item.itemId != navView.selectedItemId
}
}
})
}
Hoping it might help
Lenzy
Try this: For ChipNavigationBar
private void Bottom_navigation() {
final Fragment fragment1 = new home_fragment();
final Fragment fragment2 = new bottom_nav1_Bookmark_Fragment();
final Fragment fragment3 = new bottom_nav1_Search_Fragment();
final FragmentManager fm = getSupportFragmentManager();
ChipNavigationBar chipNavigationBar = findViewById(R.id.chipNavigation);
chipNavigationBar.setItemSelected(R.id.home, true);
fm.beginTransaction().add(R.id.frame, fragment3, "3").hide(fragment3).commit();
fm.beginTransaction().add(R.id.frame, fragment2, "2").hide(fragment2).commit();
fm.beginTransaction().add(R.id.frame,fragment1, "1").commit();
chipNavigationBar.setOnItemSelectedListener(new ChipNavigationBar.OnItemSelectedListener() {
#Override
public void onItemSelected(int i) {
Fragment active = fragment1;
Fragment fragment = null;
switch (i) {
case R.id.home:
fm.beginTransaction().hide(active).show(fragment1).commit();
active = fragment1;
chipNavigationBar.animate().translationY(0);
break;
case R.id.tajbi:
fm.beginTransaction().hide(active).show(fragment2).commit();
active = fragment2;
break;
case R.id.more_App:
fm.beginTransaction().hide(active).show(fragment3).commit();
active = fragment3;
break;
}
}
});
}
You can resolve this issue by using this solution.
First, declare fragments that are used in the bottom navigation view.
val fragment1: Fragment = HomeFragment()
val fragment2: Fragment = ProfileFragment()
val fragment3: Fragment = SettingsFragment()
val fm: FragmentManager = supportFragmentManager
var active = fragment1
Now you need to setup the bottom navigation like this
private fun setUpBottomNavigation() {
fm.beginTransaction().add(R.id.mainHostFragment, fragment3, "3").hide(fragment3).commit();
fm.beginTransaction().add(R.id.mainHostFragment, fragment2, "2").hide(fragment2).commit();
fm.beginTransaction().add(R.id.mainHostFragment,fragment1, "1").commit();
val navigation = findViewById<View>(R.id.bottomNavigationView) as BottomNavigationView
navigation.setOnItemSelectedListener(mOnNavigationItemSelectedListener)
}
private val mOnNavigationItemSelectedListener =
NavigationBarView.OnItemSelectedListener { item ->
when (item.itemId) {
R.id.homeFragment -> {
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
fm.beginTransaction().hide(active).show(fragment1).commit()
active = fragment1
}
R.id.assessmentListFragment -> {
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
fm.beginTransaction().hide(active).show(fragment2).commit()
active = fragment2
}
R.id.settingsFragment -> {
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
fm.beginTransaction().hide(active).show(fragment3).commit()
active = fragment3
}
}
false
}
Related
Error using <FragmentContainerView>, but no error with <fragment>
I am developing a mobile app and I am currently trying to rework my workflow to more appropriately leverage Activities and Fragments for their intended purposes and I have run across a strange issue I can't figure out. I have a fragment I am trying to add to an Activity, but what I try and use FragmentContainerView, the app crashes on launch, but it doesn't happen when I just use a tag with all the same attributes. In looking at the Logcat, the error comes from null being assigned to the last line of the utils file where it tries to assign to topAppBar view the view with an id of top_app_bar. Here is the relevant code: MainActivity.kt override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) fragmentManager.beginTransaction().replace(R.id.frame_layout, HomeFragment()).commit() utils = Utils(this) topAppBar = findViewById(R.id.top_app_bar) drawerLayout = findViewById(R.id.drawer_layout) val navigationView: NavigationView = findViewById(R.id.navigation_view) topAppBar.setNavigationOnClickListener { if (!drawerLayout.isDrawerOpen(GravityCompat.START)) { drawerLayout.openDrawer(GravityCompat.START) } else { drawerLayout.closeDrawer(GravityCompat.START) } } navigationView.setNavigationItemSelectedListener { item -> val id: Int = item.itemId drawerLayout.closeDrawer(GravityCompat.START) when (id) { R.id.navigation_home -> { utils.replaceFragment(HomeFragment(), getString(R.string.app_name)) } R.id.navigation_recipes -> { utils.replaceActivity(this, item.title.toString().lowercase()) } R.id.navigation_budget -> { utils.replaceActivity(this, item.title.toString().lowercase()) } R.id.navigation_inventory -> { utils.replaceActivity(this, item.title.toString().lowercase()) } R.id.navigation_customers -> { utils.replaceActivity(this, item.title.toString().lowercase()) } R.id.navigation_reports -> { utils.replaceActivity(this, item.title.toString().lowercase()) } } true } activity_main.xml <androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="#+id/fragment_container_view" android:layout_width="match_parent" android:layout_height="match_parent" android:name="com.example.bakingapp.ui.TopAppBarFragment" tools:layout="#layout/fragment_top_app_bar" /> Utils.kt (There is more to this file, but it is not relevant to the problem) class Utils(activity: Activity) { private var currentActivity: Activity private var fragmentManager: FragmentManager private var topAppBar: MaterialToolbar val activitiesList = listOf("recipes", "budget", "inventory", "customers", "reports") init { currentActivity = activity fragmentManager = (activity as AppCompatActivity).supportFragmentManager topAppBar = currentActivity.findViewById(R.id.top_app_bar) } } This works perfectly fine when I use instead of what is currently there, but I get warnings saying I shouldn't use fragment. I should be able to use the more proper tag, but I don't understand why it can't find the view when I use this method, but it can find the view when I use the tag. If someone could explain what is happening here and what I can do to fix the issue, I would really appreciate it.
Step 1 : Add FragmentContainerView to your activity xml <androidx.fragment.app.FragmentContainerView android:id="#+id/container" android:layout_width="match_parent" android:layout_height="match_parent" /> Step 2 : In your MainActivity.class file, declare FragmentManager private FragmentManager manager; Step 3 : Initialize FragmentManager in onCreate() manager = getSupportFragmentManager(); Step 4 : In your onOptionsItemSelected() begin this fragment Bundle bundle = new Bundle(); manager.beginTransaction() .replace(R.id.container/*Your View Id*/, YourFragment.class, bundle, "TAG") .setReorderingAllowed(true) //.setCustomAnimations(R.anim.anim_enter, R.anim.anim_exit) .addToBackStack("TAG") .commit();
Have you tried putting FragmentContainerView inside a layout? Instead of using it as parent layout. That could solve it.
I managed to figure out the problem. It was a scope issue. A lot of logic for a Fragment I was handling in MainActivity. I moved pretty much everything in my onCreate function into the relevant Fragment file and from there was able to get the FragmentContainerView working after refactoring this code
Issue with backstack and bottomnav in kotlin
I have a bottom nav with 4 fragments Home, Following, Notification, and Profile, there is no issue with the bottom navigation on backstack , but now for eg from profile fragment I jumped to a fragment called edit_profile which is not a part of the bottom nav and when press back I want that it should go back to the profile fragment but the backstack is taking me from edit_profile to directly home fragment here is a recording link I recently change my project from java to kotlin and I'm a beginner in kotlin i really like the navigation of Pinterest and Instagram Note:- All this code is automatically changed to kotlin (with some changes done manually ) , this issue was also with java and not after migrating to kotlin , Also if you want more reference of the code please tell me i will update the question Code MainActivity.kt // Bottom Nav class MainActivity : AppCompatActivity() { var bottomNavigationView: BottomNavigationView? = null var integerDeque: Deque<Int> = ArrayDeque(3) var flag = true #RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) setContentView(R.layout.activity_main) val window = this.window window.statusBarColor = this.resources.getColor(R.color.black) bottomNavigationView = findViewById(R.id.bottom_navigation_view) integerDeque.push(R.id.nav_home) loadFragments(Home_Fragment()) bottomNavigationView!!.selectedItemId = R.id.nav_home bottomNavigationView!!.setOnNavigationItemSelectedListener( BottomNavigationView.OnNavigationItemSelectedListener { item: MenuItem -> val id = item.itemId if (integerDeque.contains(id)) { if (id == R.id.nav_home) { integerDeque.size if (flag) { integerDeque.addFirst(R.id.nav_home) flag = false } } integerDeque.remove(id) } integerDeque.push(id) loadFragments(getFragment(item.itemId)) false } ) } #SuppressLint("NonConstantResourceId") private fun getFragment(itemId: Int): Fragment { when (itemId) { R.id.nav_home -> { bottomNavigationView!!.menu.getItem(0).isChecked = true return Home_Fragment() } R.id.nav_following -> { bottomNavigationView!!.menu.getItem(1).isChecked = true return Following_Fragment() } R.id.nav_notification -> { bottomNavigationView!!.menu.getItem(2).isChecked = true return Notification_Fragment() } R.id.nav_profile -> { bottomNavigationView!!.menu.getItem(3).isChecked = true return Profile_Fragment() } } bottomNavigationView!!.menu.getItem(0).isChecked = true return Home_Fragment() } private fun loadFragments(fragment: Fragment?) { if (fragment != null) { supportFragmentManager.beginTransaction() .replace(R.id.fragment_container, fragment, fragment.javaClass.simpleName) .commit() } } override fun onBackPressed() { integerDeque.pop() if (!integerDeque.isEmpty()) { loadFragments(getFragment(integerDeque.peek())) } else { finish() } } Edit_Profile.kt // from this fragment i want to go back to the last fragment which should be the profile fragment class Edit_Profile : Fragment() { private var profilePhoto: CircleImageView? = null override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = inflater.inflate(R.layout.fragment_edit_profile, container, false) profilePhoto = view.findViewById(R.id.circleImageView) initImageLoader() setProfileImage() val imageView = view.findViewById<ImageView>(R.id.backArrow) imageView.setOnClickListener { val newCase: Fragment = Profile_Fragment() assert(fragmentManager != null) val transaction = requireFragmentManager().beginTransaction() transaction.replace(R.id.fragment_container, newCase) transaction.addToBackStack(Profile_Fragment.toString()) transaction.commit() } return view } Edit added a part of the transaction from Profile Fragment to Edit Profile ProfileFragment.kt editProfileButton!!.setOnClickListener(View.OnClickListener { v: View? -> val edit_profile: Fragment = Edit_Profile() requireActivity().getSupportFragmentManager() .beginTransaction() .add(R.id.fragment_container, edit_profile,"TAG") .addToBackStack("TAG") .commit() })
Now you are managing the back stack through the integerDeque array. When you go to a new BottomNavigationView fragment; you added its id to the array if it doesn't already exist. When you pop up the back stack; the fragment at the top is kicked off the array. But since you pushed all those ids in the bottomNavigationView.setOnItemSelectedListener callback; then the integerDeque array only contains BottomNavigationView fragments ids. And as the Edit_Profile fragment is not a part of BottomNavigationView fragments, then it won't be added/popped off the queue. Instead when you try to popup the back stack whenever the Edit_Profile fragment is shown; the normal behavior you manage in the onBackPressed() continues and the Profile_Fragment id will pop up from the queue making you return to the preceding fragment (Home_Fragment) in your mentioned example. A little fix to this is to consider adding an id into the queue when you transact to Edit_Profile fragment so that this id is popped off the queue resulting in back to Profile_Fragment fragment. You can do that with the fragment's id in order to make sure it's unique: editProfileButton!!.setOnClickListener(View.OnClickListener { v: View? -> val edit_profile: Fragment = Edit_Profile() requireActivity().getSupportFragmentManager() .beginTransaction() .add(R.id.fragment_container, edit_profile,"TAG") .addToBackStack("TAG") .commit() (requireActivity() as MainActivity).integerDeque.push(id) // <<<< pushing id to the queue }) This should fix your problem. Side tips: Use setOnItemSelectedListener instead of setOnNavigationItemSelectedListener on the BNV as the latter is deprecated. Return true instead of false from setOnItemSelectedListener callback as this should consume the event and mark the BNV as selected. In Edit_Profile transaction replace the fragment instead of adding it with add as already the container is consumed; and this would make you avoid overlapping fragments in the container. In onBackPressed(); you'd replace loadFragments(..) with bottomNavigationView.selectedItemId = integerDeque.peek(); this could be lighter to reuse the same fragment instead of redoing the transaction.
Usually I follow this pattern Where I add HomeF in main container which includes all bottom nav tab, and all bottom nav tab will open in home container, and those fragment which are not part of bottom nav will open in main container. I generally add(not replace) all the fragments in main container and set add to back stack , so that if user goes from profile (home_container) to something in main container , while backstack we can pop the top fragment and user will be seeing profile.
Android Bottom navigation with splash
I'm implementing this bottom navigation pattern but with a splash fragment. My issue is when I navigate throw different fragments with bottom menu and I press to go back, I don't go back to the home fragment, instead of this, I return to the previous fragment. For example, I have fragments A-B-C: Now I'm on fragment A and I press to go to B. Then I press to go to C (from B). Then I press to go back. The result is I'm getting back to B, not to fragment A (what I really want!). (If I set in the navigation graph app:startDestination -> fragment A - instead of login fragment - everything goes well). Here is my graph: <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/main_navigation" app:startDestination="#id/splashFragment"> <fragment android:id="#+id/splashFragment" android:name="application.SplashFragment" tools:layout="#layout/fragment_splash"> <action android:id="#+id/action_splashFragment_to_fragmentA" app:destination="#id/fragmentA" app:popUpTo="#id/main_navigation" app:popUpToInclusive="true" /> </fragment> <fragment android:id="#+id/fragmentA" android:name="application.fragmentA" android:label="#string/fragmentA" tools:layout="#layout/fragmentA" /> <fragment android:id="#+id/fragmentB" android:name="application.fragmentB" android:label="fragmentB" tools:layout="#layout/fragmentB" /> <fragment android:id="#+id/fragmentC" android:name="application.fragmentC" android:label="#string/fragmentC" tools:layout="#layout/fragmentC" /> And here my MainActivity: class MainActivity : AppCompatActivity() { private lateinit var appBarConfiguration: AppBarConfiguration private lateinit var navController: NavController override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Toolbar & Navigation setSupportActionBar(toolbar) navController = findNavController(R.id.nav_host) // AppBarConfiguration with the correct top-level destinations appBarConfiguration = AppBarConfiguration( topLevelDestinationIds = setOf( R.id.fragmentA, R.id.fragmentB, R.id.fragmentC ) ) // This allows NavigationUI to decide what label to show in the action bar // By using appBarConfig, it will also determine whether to // show the up arrow or drawer menu icon setupActionBarWithNavController(navController, appBarConfiguration) // Set destinations to left and bottom navigation bottom_navigation.setupWithNavController(navController) // Set visibility for NavigationView & Toolbar visibilityNavElements() } // Allows NavigationUI to support proper up navigation or the drawer layout // drawer menu, depending on the situation override fun onSupportNavigateUp() = navController.navigateUp(appBarConfiguration) private fun visibilityNavElements() { findNavController(R.id.nav_host).addOnDestinationChangedListener { _, destination, _ -> when (destination.id) { R.id.splashFragment -> { toolbar.visibility = View.GONE bottom_navigation.visibility = View.GONE } else -> { toolbar.visibility = View.VISIBLE bottom_navigation.visibility = View.VISIBLE } } } } } Thanks!
You can handle OnbackPress in any fragment like below and is the recommended way . You can use it in Fragment C and when back is pressed you can navigate to Fragment A in Java OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) { #Override public void handleOnBackPressed() { } }; requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback); in Kotlin val callback = requireActivity().onBackPressedDispatcher.addCallback(this) { // Handle the back button event }
Issues while changing Back button behavior with Navigation Drawer (Android/Kotlin)
Building my first custom app, I would like to implement something that seemed easy at first but of course it is not. I have an activity with some fragments, which I can navigate to with a Navigation Drawer: res/menu/navdrawer_menu.xml <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="#+id/matchFragmentUI" android:icon="#drawable/ic_match" android:title="#string/matches_string" /> <item android:id="#+id/statFragmentUI" android:icon="#drawable/ic_stat" android:title="#string/stats_string" /> <item android:id="#+id/clubFragmentUI" android:icon="#drawable/ic_club" android:title="#string/club_string" /> <item android:id="#+id/agendaFragmentUI" android:icon="#drawable/ic_agenda" android:title="#string/agenda_string" /> <item android:id="#+id/settingFragmentUI" android:icon="#drawable/ic_settings" android:title="#string/settings_string" /> <item android:id="#+id/profileFragmentUI" android:icon="#drawable/ic_person_black_24dp" android:title="#string/profile_text" /> </menu> res/layout/activity_content.xml <?xml version="1.0" encoding="utf-8"?> <layout 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"> <androidx.drawerlayout.widget.DrawerLayout android:id="#+id/drawerLayout" android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".content.ContentActivity"> <fragment android:id="#+id/contentNavHostFragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:navGraph="#navigation/navigation_content" /> </androidx.constraintlayout.widget.ConstraintLayout> <com.google.android.material.navigation.NavigationView android:id="#+id/navView" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" app:menu="#menu/navdrawer_menu" app:headerLayout="#layout/nav_header"/> </androidx.drawerlayout.widget.DrawerLayout> </layout> I would like to change the back button behavior to prevent users from leaving accidentally the app like this : /* Override behavior of back button */ override fun onBackPressed() { /* Case we are not on the home screen */ if(/* not on home screen */) /* go to home screen */ else { /* Otherwise we return to the access activity */ /* Dialog window to prevent accidental leaving */ val backAlertDialog = android.app.AlertDialog.Builder(this) backAlertDialog.setTitle("Log out") backAlertDialog.setMessage("Are you sure you want to leave?") /* buttons YES/NO actions */ backAlertDialog.setPositiveButton("No") { dialog, which -> dialog.cancel() } backAlertDialog.setNegativeButton("Yes") { dialog, which -> val accessIntent = Intent(this, AccessActivity::class.java) accessIntent.flags = FLAG_ACTIVITY_CLEAR_TOP // Clear activity backstack startActivity(accessIntent) // Go back to access activity finish() // Finish this activity } backAlertDialog.show() } } My problem is checking if we are or not displaying the HomeFragment. At first, I tried with this : if (supportFragmentManager.backstackEntryCount > 0) But the issue here is that navigating with the navigation drawer doesn't add anything to the backstack (the backstackEntryCount stays at 0) so it won't work. That's why I'm now trying to use NavigationView.OnNavigationItemSelectedListener interface, to add something in the backstack when an item is selected and also keep track of what is the current displayed fragment like : /* Keep track of what the current fragment is like */ private var fragment : Fragment? = null override fun onNavigationItemSelected(item: MenuItem): Boolean { val itemId = item.itemId val fm = this.supportFragmentManager /* Clear stack */ fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) /* Add a case for each item in the drawer */ /* For each case, always add (push) a HomeFragment and then the fragment we navigate to */ /* Then, going back will always return to Home because we cleared backstack before */ when(itemId) { R.id.profileFragmentUI -> { fragment = ProfileFragmentUI() } R.id.agendaFragmentUI -> { fragment = AgendaFragmentUI() } R.id.clubFragmentUI -> { fragment = ClubFragmentUI() } R.id.statFragmentUI -> { fragment = StatFragmentUI() } R.id.matchFragmentUI -> { fragment = MatchFragmentUI() } R.id.settingFragmentUI -> { fragment = SettingFragmentUI() } } /* Replace current fragment by the selected fragment in backstack and add home for back support */ if (fragment != null) { fm.beginTransaction() .add(HomeFragmentUI(), "HomeFragmentUI") .addToBackStack("HomeFragmentUI") .replace(R.id.contentNavHostFragment, fragment!!) .commit() } return true } Also, here are my onCreate() and onSupportNavigateUp() methods in the activity code : override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) /* Bind layout elements at compile time to be faster at runtime */ val binding = DataBindingUtil.setContentView<ActivityContentBinding>(this, R.layout.activity_content) val contentDrawerLayout = binding.drawerLayout val player = intent.getParcelableExtra("player") val navigationView = binding.navView navigationView.setNavigationItemSelectedListener { item -> onNavigationItemSelected(item) } /* Setup the navigation for fragments */ val navController = this.findNavController(R.id.contentNavHostFragment) val fragmentManager = this.supportFragmentManager /* Setup navigation bar, with drawer menu */ NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout) NavigationUI.setupWithNavController(binding.navView, navController) fragment = HomeFragmentUI() /* To make back button work at start of navigation */ } override fun onSupportNavigateUp(): Boolean { val navController = this.findNavController(R.id.contentNavHostFragment) /* Replace up button by drawer button on title screen */ return NavigationUI.navigateUp(navController, drawerLayout) } Now, I want to do my check like this : if (fragment is HomeFragmentUI) /* display dialog */ else /* navigate back to home */ The problem now is that when I select an item in my drawer, I navigate to the good fragment, BUT the attribute fragment is still a HomeFragmentUI instance. I tried to put some logs in onNavigationItemSelected method but they never show up. I have no idea of why the method is never called since I defined the listener in onCreate. Here are some links I used until now : https://www.youtube.com/watch?v=I47Zy6JtNIU https://www.youtube.com/watch?v=y9KYKYJJ4GY https://guides.codepath.com/android/Fragment-Navigation-Drawer Thank you in advance.
Android Navigation Architecture Component - Get current visible fragment
Before trying the Navigation component I used to manually do fragment transactions and used the fragment tag in order to fetch the current fragment. val fragment:MyFragment = supportFragmentManager.findFragmentByTag(tag):MyFragment Now in my main activity layout I have something like: <fragment android:layout_width="match_parent" android:layout_height="match_parent" android:id="#+id/nav_host" app:navGraph= "#navigation/nav_item" android:name="androidx.navigation.fragment.NavHostFragment" app:defaultNavHost= "true" /> How can I retrieve the current displayed fragment by the Navigation component? Doing supportFragmentManager.findFragmentById(R.id.nav_host) returns a NavHostFragment and I want to retrieve my shown 'MyFragment`. Thank you.
I managed to discover a way for now and it is as follows: NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host); navHostFragment.getChildFragmentManager().getFragments().get(0); In case of course you know it is the first fragment. I am still investigating a way without this. I agree it is not the best way but that should be something for now.
There is no way I can find to retrieve the current fragment instance. However, you can get the ID of lastly added fragment using the code below. navController.currentDestination?.getId() It returns the ID in navigation file. Hope this helps.
You should look the childFragmentManager primaryNavigationFragment property of your nav fragment, this is where the current displayed fragment is referenced. val navHost = supportFragmentManager.findFragmentById(R.id.main_nav_fragment) navHost?.let { navFragment -> navFragment.childFragmentManager.primaryNavigationFragment?.let {fragment-> //DO YOUR STUFF } } }
This seems like the cleanest solution. Update: Changed extension function to property. Thanks Igor Wojda. 1. Create the following extension import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager val FragmentManager.currentNavigationFragment: Fragment? get() = primaryNavigationFragment?.childFragmentManager?.fragments?.first() 2. In Activity with NavHostFragment use it like this: val currentFragment = supportFragmentManager.currentNavigationFragment
Use addOnDestinationChangedListener in activity having NavHostFragment For Java NavController navController= Navigation.findNavController(MainActivity.this,R.id.your_nav_host); navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() { #Override public void onDestinationChanged(#NonNull NavController controller, #NonNull NavDestination destination, #Nullable Bundle arguments) { Log.e(TAG, "onDestinationChanged: "+destination.getLabel()); } }); For Kotlin var navController :NavController=Navigation.findNavController(this, R.id.nav_host_fragment) navController.addOnDestinationChangedListener { controller, destination, arguments -> Log.e(TAG, "onDestinationChanged: "+destination.label); }
this might be late but for anyone who's still looking val currentFragment = NavHostFragment.findNavController(nav_host_fragment).currentDestination?.id then you can do something like if(currentFragment == R.id.myFragment){ //do something } note that this is for kotlin, java code should be almost the same
My requirement is to get the current visible fragment from Parent activity,my solution is private Fragment getCurrentVisibleFragment() { NavHostFragment navHostFragment = (NavHostFragment)getSupportFragmentManager().getPrimaryNavigationFragment(); FragmentManager fragmentManager = navHostFragment.getChildFragmentManager(); Fragment fragment = fragmentManager.getPrimaryNavigationFragment(); if(fragment instanceof Fragment ){ return fragment ; } return null; } This may help some one with same problem.
navController().currentDestination?.id will give you your current Fragment's id where private fun navController() = Navigation.findNavController(this, R.id.navHostFragment) this id is the id which you have given in your Navigation Graph XML file under fragment tag. You could also compare currentDestination.label if you want.
First, you get the current fragment by id, then you can find the fragment, depending on that id. int id = navController.getCurrentDestination().getId(); Fragment fragment = getSupportFragmentManager().findFragmentById(id);
Found working solution. private fun getCurrentFragment(): Fragment? { val currentNavHost = supportFragmentManager.findFragmentById(R.id.nav_host_id) val currentFragmentClassName = ((this as NavHost).navController.currentDestination as FragmentNavigator.Destination).className return currentNavHost?.childFragmentManager?.fragments?.filterNotNull()?.find { it.javaClass.name == currentFragmentClassName } }
As per #slhddn answer and comment this is how I'm using it and retrieving the fragment id from the nav_graph.xml file: class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) setSupportActionBar(findViewById(R.id.toolbar)) val navController = findNavController(this, R.id.nav_host_fragment) ivSettingsCog.setOnClickListener { if (navController.currentDestination?.id == R.id.updatesFragment) // Id as per set up on nav_graph.xml file { navController.navigate(R.id.action_updatesFragment_to_settingsFragment) } } } }
Using navigation components, I got the current displayed fragment using this line of code: val fragment = parentFragmentManager.primaryNavigationFragment
From an Activity that uses NavHostFragment, you can use the code below to retrieve the instance of the Active Fragment. val fragmentInstance = myNavHostFragment?.childFragmentManager?.primaryNavigationFragment
Within your activity, hosting the NavHostFragment we can write the following piece of code to fetch the instance of the currently displayed fragment Fragment fragment = myNavHostFragment.getChildFragmentManager().findFragmentById(R.id.navHostFragmentId) if(fragment instanceOf MyFragment) { //Write your code here } Here R.id.navHostFragmentId is the resId for the NavHostFragment within your activity xml
Make : • in some button in your fragment: val navController = findNavController(this) navController.popBackStack() • In your Activity onBackPressed() override fun onBackPressed() { val navController = findNavController(R.id.nav_host_fragment) val id = navController.currentDestination?.getId() if (id == R.id.detailMovieFragment){ navController.popBackStack() return } super.onBackPressed() }
The first fragment in navhost fragment is the current fragment Fragment navHostFragment = getSupportFragmentManager().getPrimaryNavigationFragment(); if (navHostFragment != null) {Fragment fragment = navHostFragment.getChildFragmentManager().getFragments().get(0);}
Finally the working solution for those of you who are still searching Actually it is very simple and you can achieve this using callback. follow these steps: Create a listener inside the fragment you want to get the instance from activity onCreate() public class HomeFragment extends Fragment { private OnCreateFragment createLIstener; public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { createLIstener.onFragmentCreated(this); View root = inflater.inflate(R.layout.fragment_home, container, false); //findViews(root); return root; } #Override public void onCreateOptionsMenu(#NonNull Menu menu, #NonNull MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.menu_main, menu); } #Override public boolean onOptionsItemSelected(#NonNull MenuItem item) { if (item.getItemId()==R.id.action_show_filter){ //do something } return super.onOptionsItemSelected(item); } #Override public void onAttach(Context context) { super.onAttach(context); try { createLIstener = (OnCreateFragment) context; } catch (ClassCastException e) { throw new ClassCastException(context.toString() + " must implement OnArticleSelectedListener"); } } public interface OnCreateFragment{ void onFragmentCreated(Fragment f); } } Implement your listener in the host Fragment/Activity public class MainActivity extends AppCompatActivity implements HomeFragment.OnCreateFragment { #Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); BottomNavigationView navView = findViewById(R.id.nav_view); // Passing each menu ID as a set of Ids because each // menu should be considered as top level destinations. AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder( R.id.navigation_home, R. ----) .build(); NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment); NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration); NavigationUI.setupWithNavController(navView, navController); } #Override public void onFragmentCreated(Fragment f) { if (f!=null){ H.debug("fragment created listener: "+f.getId()); f.setHasOptionsMenu(true); }else { H.debug("fragment created listener: NULL"); } } } And that is everything you need to do the job. Hop this help someone
The best way I could figure out with 1 fragment, Kotlin, Navigation usage: On your layout file add the tag: "android:tag="main_fragment_tag"" <fragment android:layout_width="match_parent" android:layout_height="match_parent" android:id="#+id/nav_host" app:navGraph= "#navigation/nav_item" android:tag="main_fragment_tag" android:name="androidx.navigation.fragment.NavHostFragment" app:defaultNavHost= "true" /> On your Activity: val navHostFragment = supportFragmentManager.findFragmentByTag("main_fragment_tag") as NavHostFragment val mainFragment = navHostFragment.childFragmentManager.fragments[0] as MainFragment mainFragment.mainFragmentMethod()
Here is my solution for java NavHostFragment navHostFragment= (NavHostFragment) getSupportFragmentManager().getFragments().get(0); MyFragment fragment = (MyFragment) navHostFragment.getChildFragmentManager().getFragments().get(0); fragment.doSomething();
create extension function on Fragment Manager inline fun <reified T : Fragment> FragmentManager.findNavFragment(hostId: Int): T? { val navHostFragment = findFragmentById(hostId) return navHostFragment?.childFragmentManager?.fragments?.findLast { it is T } as T? } Now simply use it by giving id of the hosting fragment and get the specified fragment val myFragment: MyFragment? = supportFragmentManager.findNavFragment<MyFragment>(R.id.nav_host_id)
can you please check with getChildFragmentManager() call with NavHostFragment fragment = supportFragmentManager.findFragmentById(R.id.nav_host); MyFragment frag = (MyFragment) fragment.getChildFragmentManager().findFragmentByTag(tag);
If you need the instance of your fragment, you can go with: (Activity) => supportFragmentManager.fragments.first().childFragmentManager.fragments.first() It's very ugly but from there you can check if it's an instance of the fragment you want to play around with
On Androidx, I have tried many methods for finding out required fragment from NavHostFragment. Finally I could crack it with below method public Fragment getFunctionalFragment (String tag_name) { NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment); FragmentManager navHostManager = Objects.requireNonNull(navHostFragment).getChildFragmentManager(); Fragment fragment=null; List fragment_list = navHostManager.getFragments(); for (int i=0; i < fragment_list.size() ; i ++ ) { if ( fragment_list.get(i) instanceof HomeFragment && tag_name.equals("frg_home")) { fragment = (HomeFragment) fragment_list.get(i); break; } } return fragment; }
This code works from me NavDestination current = NavHostFragment.findNavController(getSupportFragmentManager().getPrimaryNavigationFragment().getFragmentManager().getFragments().get(0)).getCurrentDestination(); switch (current.getId()) { case R.id.navigation_saved: // WRITE CODE break; }
this might be late but may help someone later. if you are using nested navigations you can get id like this in Kotlin val nav = Navigation.findNavController(requireActivity(), R.id.fragment_nav_host).currentDestination and this is one of fragments in fragment_nav_host.xml <fragment android:id="#+id/sampleFragment" android:name="com.app.sample.SampleFragment" android:label="SampleFragment" tools:layout="#layout/fragment_sample" /> and you can check all you need like this if (nav.id == R.id.sampleFragment || nav.label == "SampleFragment"){}
val fragment: YourFragment? = yourNavHost.findFragment() Note: findFragment extension function is located in androidx.fragment.app package, and it's generic function
I combined Andrei Toaders answer and Mukul Bhardwajs answer with an OnBackStackChangedListener. As an attribute: private FooFragment fragment; In my onCreate NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host); FragmentManager navHostManager = Objects.requireNonNull(navHostFragment).getChildFragmentManager(); FragmentManager.OnBackStackChangedListener listener = () -> { List<Fragment> frags = navHostManager.getFragments(); if(!frags.isEmpty()) { fragment = (FooFragment) frags.get(0); } }; navController.addOnDestinationChangedListener((controller, destination, arguments) -> { if(destination.getId()==R.id.FooFragment){ navHostManager.addOnBackStackChangedListener(listener); } else { navHostManager.removeOnBackStackChangedListener(listener); fragment = null; } }); This way i can get a fragment, which will be displayed later on. One even may be able to loop though frags and check multiple fragments with instanceof.
I need to do this to setup a bottom navigation view and I did it this way: val navController = Navigation.findNavController(requireActivity(), R.id.nav_tabs_home) val bottomNavBar: BottomNavigationView = nav_content_view NavigationUI.setupWithNavController(bottomNavBar, navController)
The best way to achieve this is to register a fragment lifecycle callback. As per the original question, if your NavHostFragment is defined as... <fragment android:layout_width="match_parent" android:layout_height="match_parent" android:id="#+id/nav_host" app:navGraph= "#navigation/nav_item" android:name="androidx.navigation.fragment.NavHostFragment" app:defaultNavHost= "true" /> Then in your main activity onCreate(..)... nav_host.childFragmentManager.registerFragmentLifecycleCallbacks( object:FragmentManager.FragmentLifecycleCallbacks() { override fun onFragmentViewCreated( fm: FragmentManager, f: Fragment, v: View, savedInstanceState: Bundle? ) { // callback method always called whenever a // fragment is added, or, a back-press returns // to the start-destination } } ) Other callback methods may be overridden to fit your requirements.
This solution will work in most situations Try this: val mainFragment = fragmentManager?.primaryNavigationFragment?.parentFragment?.parentFragment as MainFragment or this: val mainFragment = fragmentManager?.primaryNavigationFragment?.parentFragment as MainFragment