i have a problem with navigation between fragments in Navbar.
This is my situation:
I have 3 main fragments: Home, List, Settings
Home has 2 sub fragment: Add & Edit
So, if i navigate between the 3 main fragments with navbar is everything ok, then if i go in one of 2 "sub fragments" and then, (instead of "navigate up" before and then change main) i directly choose one of other 2 "main" i will go there, but then if i come back to List i will see the sub fragment.
I have to Navigate Up to see again List.
**What i want is **if i switch main fragment when im inside of one of the "subs"(son of List), and then again i choose List, i want see List and not the sub that i left.
My Code:
Navigation
<fragment
android:id="#+id/homeFragment"
android:name="com.somi.fidelitycardconnect.ui.home.HomeFragment"
android:label="Home"
tools:layout="#layout/fragment_home" />
<fragment
android:id="#+id/settingsFragment"
android:name="com.somi.fidelitycardconnect.ui.settings.SettingsFragment"
android:label="Impostazioni"
tools:layout="#layout/fragment_settings" />
<fragment
android:id="#+id/listFragment"
android:name="com.somi.fidelitycardconnect.ui.list.ListFragment"
android:label="Lista clienti"
tools:layout="#layout/fragment_list">
<action
android:id="#+id/action_listFragment_to_addFormFragment"
app:destination="#id/addFormFragment" />
<action
android:id="#+id/action_listFragment_to_editFormFragment"
app:destination="#id/editFormFragment" />
</fragment>
<fragment
android:id="#+id/addFormFragment"
android:name="com.somi.fidelitycardconnect.ui.form.AddFormFragment"
android:label="Aggiungi cliente"
tools:layout="#layout/fragment_add_form">
</fragment>
<fragment
android:id="#+id/editFormFragment"
android:name="com.somi.fidelitycardconnect.ui.form.EditFormFragment"
android:label="Informazioni cliente"
tools:layout="#layout/fragment_edit_form">
</fragment>
MainActivity:
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val navView: BottomNavigationView = binding.navView
val navController = findNavController(R.id.nav_host_fragment_activity_main)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.homeFragment,
R.id.listFragment,
R.id.settingsFragment
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment_activity_main)
return navController.navigateUp(appBarConfiguration)
|| super.onSupportNavigateUp()
}
}
enter image description here
enter image description here
**What i want is **if i switch main fragment when im inside of one of the "subs"(son of List), and then again i choose List, i want see List and not the sub that i left.
If you want to press one of the bottomNav items and start with the default position, you need to pop the other destinations. Use below code to achieve it:
navView.setupWithNavController(navController)
navView.setOnItemSelectedListener { item ->
NavigationUI.onNavDestinationSelected(item, navController)
navController.popBackStack(item.itemId, inclusive = false)
true
}
I have a BottomNavigationView and I am using a NavController to switch menus and move the screen.
I would like to know how to keep a specific screen even when moving the menu freely.
Let me explain in more detail.
There are bottom menus A, B, C. And each has a Fragment screen.
All menus and screen transitions use NavHost, NavController, and nav_graph.
In menu C, a dialog is displayed through the button on the C screen.
The dialog is also linked to the nav_graph.
When an option is selected in the dialog, it moves to screen D.
D screen is the page where you write something.
This is important from now on.
The current menu is C, screen D is open, and something is being written.
However, if you go to another menu while writing and then return to C, screen C of the first menu C appears, not the screen you are writing.
Here, I want the screen I was writing to continue to be displayed even when I return from another menu.
Any good way?
For reference, since I am using the mvvm pattern and viewmodel, the data of the screen I am writing seems to be maintained.
Thank you.
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/calendar">
<!-- menu C screen -->
<fragment
android:id="#+id/write_home"
android:name="com.example.writeweight.fragment.WriteRoutineHomeFragment"
android:label="fragment_write_routine_home"
tools:layout="#layout/fragment_write_routine_home" >
<action
android:id="#+id/action_write_home_to_bodyPartDialog"
app:destination="#id/bodyPartDialog" />
</fragment>
<!-- dialog -->
<dialog
android:id="#+id/bodyPartDialog"
android:name="com.example.writeweight.fragment.BodyPartDialogFragment"
android:label="BodyPartDialogFragment"
tools:layout="#layout/fragment_body_part_dialog">
<action
android:id="#+id/action_bodyPartDialog_to_write"
app:destination="#id/write"/>
</dialog>
<!-- screen D (write page) -->
<fragment
android:id="#+id/write"
android:name="com.example.writeweight.fragment.WriteRoutineFragment"
android:label="WritingRoutineFragment"
tools:layout="#layout/fragment_writing_routine">
<action
android:id="#+id/action_write_to_workoutListTabFragment"
app:destination="#id/workoutListTabFragment" />
<argument
android:name="title"
app:argType="string"
app:nullable="true"
android:defaultValue="#null"/>
<argument
android:name="workout"
app:argType="string"
app:nullable="true"
android:defaultValue="#null"/>
</fragment>
</navigation>
There are two ways to fix this issue either use version_navigation 2.4.0-alpha01 which is the easiest way or use NavigationExtensions
to use NavigationExtensions you have to add this to your project NavigationExtensions and then in the navigation menu create separate navigation files for each tab.
In the main activity, layout replace fragment with FragmentContainerView and remove NavHostFragment.
In the Mainactivty
class MainActivity : AppCompatActivity(), NavController.OnDestinationChangedListener {
private val viewModel by viewModels<MainViewModel>()
private var currentNavController: LiveData<NavController>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
setSupportActionBar(toolbar)
/*
appBarConfiguration = AppBarConfiguration(
// navController.graph,
setOf(
R.id.navigate_home, R.id.navigate_collection, R.id.navigate_profile
),
drawerLayout
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
bottomNavView.setupWithNavController(navController)
// make sure appbar/toolbar is not hidden upon fragment switch
navController.addOnDestinationChangedListener { controller, destination, arguments ->
if (destination.id in bottomNavDestinationIds) {
appBarLayout.setExpanded(true, true)
}
}
*/
// Add your tab fragments
val navGraphIds = listOf(R.navigation.home, R.navigation.albumlist, R.navigation.test)
val controller = bottomNavView.setupWithNavController(
navGraphIds = navGraphIds,
fragmentManager = supportFragmentManager,
containerId = R.id.nav_host_container,
intent = intent
)
// Whenever the selected controller changes, setup the action bar.
controller.observe(this, Observer { navController ->
setupActionBarWithNavController(navController)
// optional NavigationView for Drawer implementation
// navView.setupWithNavController(navController)
addOnDestinationChangedListener(navController)
})
currentNavController = controller
}
private fun addOnDestinationChangedListener(navController: NavController) {
// ensure only one listener is active
navController.removeOnDestinationChangedListener(this)
navController.addOnDestinationChangedListener(this)
}
override fun onDestinationChanged(
controller: NavController,
destination: NavDestination,
arguments: Bundle?
) {
if (destination.id in bottomNavDestinationIds) {
appBarLayout.setExpanded(true, true)
}
}}
I need to navigate a stack of fragments and I am navigating back using the toolbar back button. Can I override the back button pressed to set a custom animation, for example slide out?
Here is the code for the toolbar.
private fun setupToolbar() {
val appBarConfiguration = AppBarConfiguration(navController.graph, drawer_layout)
val toolbar = toolbar as Toolbar
setSupportActionBar(toolbar)
toolbar.setupWithNavController(navController, appBarConfiguration)
val ab: ActionBar? = supportActionBar
ab?.setDisplayShowTitleEnabled(false) // disable the default title element here (for centered title)
setupSearchQueryListener()
}
In your setup code, one more thing is needed:
toolbar.setNavigationOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
In your fragments, you could do this:
protected lateinit var backPressedCallback: OnBackPressedCallback
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
backPressedCallback = requireActivity().onBackPressedDispatcher.addCallback(this) {
// your code
}
}
The above will enable you to intercept back navigation uniformly and execute your code in question (you can even block/unblock it by playing with backPressedCallback.isEnabled flag). The above was tested. Speaking of setting navigation animation, I was only playing with xml defined action based animations:
<action
android:id="#+id/toYourDest"
app:destination="#+id/yourDest"
app:enterAnim="#anim/your_slide_in_right"
app:exitAnim="#anim/your_slide_out_left"
app:popEnterAnim="#anim/your_slide_in_left"
app:popExitAnim="#anim/your_slide_out_right" />
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.
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
}