I'm hiding a bottom navigation item when user is not authenticated (that item will show up once the user is authenticated, which in this case, is a Messages Fragment). That works well, but when that fragment is shown and selected, the previous navigation menu that I was in is still highlighted, even if I'm on that fragment. Also, once I chose another navigation item, Messages is still highlighted on the bottom navigation bar. Refer to the screenshots below:
When the Messages is not yet selected
When I select Message Fragment (I was from Home Fragment before that)
When I go back to any other Fragment aside from Messages (in this case, Home Fragment)
Here's my MainActivity (Notice that there's navView.menu.findItem(R.id.navigation_messages).isVisible, which is the main cause of this behavior in bottom navigation bar)
class MainActivity : AppCompatActivity() {
private var authState : Int = 1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navView: BottomNavigationView = findViewById(R.id.nav_view)
val navController = findNavController(R.id.nav_host_fragment)
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_home, R.id.navigation_explore, R.id.navigation_messages, R.id.navigation_account
)
)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
when(authState) {
0 -> navView.menu.findItem(R.id.navigation_messages).isVisible = false
1 -> navView.menu.findItem(R.id.navigation_messages).isVisible = true
}
}
override fun onSupportNavigateUp(): Boolean {
return navigateUp(Navigation.findNavController(this, R.id.nav_host_fragment), AppBarConfiguration(setOf(
R.id.navigation_home, R.id.navigation_explore, R.id.navigation_messages, R.id.navigation_account
)))
}
}
Also, here's my activity_main.xml file:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:itemHorizontalTranslationEnabled="false"
app:labelVisibilityMode="labeled"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="#menu/bottom_nav_menu" />
<fragment
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
android:layout_marginBottom="56dp"
app:layout_constraintBottom_toTopOf="#+id/nav_view"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:navGraph="#navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
To note, I'm using Android Jetpack Navigation Architecture Components, which made other similar solutions like this, this, or this, not that viable on this case.
Related
I have an error when trying to use startActivity(intent) when an item is clicked on the navigation bar. Where am I going with my code?
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var firebaseAuth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
firebaseAuth = FirebaseAuth.getInstance()
binding.logout.setOnClickListener {
firebaseAuth.signOut()
val intent = Intent(this, Login::class.java)
startActivity(intent)
}
//here is the buttom Nav bar, the setOnNavItemListener is also striken through
binding.bottomNavView.setOnNavigationItemSelectedListener {
when(it.itemId){
error is here when I try to start another activity after click
R.id.drinks -> startActivity(this, drinks::class.java)
R.id.profileScreen ->
R.id.others ->
}
}
}
}
Below is a best practice model to implement Bottom Nav using Kotlin:
Now bottom nav is integrated with nav controller
Bottom Navigation works with the concept of fragments.
Each of the pages which appear on clicking the bottom nav item is called a fragment.
Bottom Nav item = Fragment
Creating Menu Items
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/home"
android:title="#string/home"
android:icon="#drawable/ic_baseline_home_24"/>
<item
android:id="#+id/message"
android:title="#string/messages"
android:icon="#drawable/ic_baseline_message_24"/>
<item
android:id="#+id/profile"
android:title="#string/profile"
android:icon="#drawable/ic_baseline_profile_24"/>
</menu>
Adding Bottom Nav in Activity main.xml
<com.google.android.material.bottomnavigation.BottomNavigationView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="#color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
Add Navigation Resource file
In bottom_nav.xml, add the 3 fragments - Home, Messages and Profile
All these 3 are top level fragments and not linked to each other
Important: Make Sure that the id of fragments in navigation resource file is same as the id of menu items
Add Nav Host Fragment to Activity Main.xml
Drag and Drop - nav host fragment to activity main.xml and add bottom_nav.xml created in previous step to it
In bottom navigation component in activity main.xml, add app:menu="#menu/bottom_menu" to link the menu
<androidx.fragment.app.FragmentContainerView
android:id="#+id/fragmentContainerView"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="#id/bottomNavigationView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/my_nav"
/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNavigationView"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:menu="#menu/bottom_menu"
android:background="#color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
MainActivity.kt
We need to activate the respective menus in MainActivity file
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Setting bottom nav with nav controller
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottomNavigationView)
val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
val navController = navHostFragment.navController
bottomNavigationView.setupWithNavController(navController)
}
This is how I'm implementing bottom navigation
<com.google.android.material.bottomnavigation.BottomNavigationView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/main_botton_navigation"
android:background="#color/btn_bg"
app:menu="#menu/main_bottom_nav_menu"
app:labelVisibilityMode="labeled"
app:itemTextColor="#drawable/text_selector"
app:layout_constraintBottom_toBottomOf="parent" />
<fragment
android:id="#+id/fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
app:layout_constraintTop_toBottomOf="#id/main_appbar"
android:layout_height="617dp"
app:layout_constraintBottom_toTopOf="#id/main_botton_navigation"
app:defaultNavHost="true"
app:navGraph="#navigation/main_bottom_nav"
tools:layout_editor_absoluteX="1dp"
tools:layout_editor_absoluteY="57dp" />
and this is how I'm doing in activity
val navController = findNavController(R.id.fragment)
val appBarConfiguration =
AppBarConfiguration(setOf(R.id.allFiles, R.id.recentFiles, R.id.bookMark, R.id.tools))
setupActionBarWithNavController(navController, appBarConfiguration)
binding.mainBottonNavigation.setupWithNavController(navController)
but only last activity is changed while clicking on last item other's are not changing
Change your code like this and change fragment name according to yours.
// Setting Up ActionBar with Navigation Controller
// Pass the IDs of top-level destinations in AppBarConfiguration
var appBarConfiguration = AppBarConfiguration(
topLevelDestinationIds = setOf (
R.id.homeFragment,
R.id.searchFragment,
R.id.notificationsFragment,
R.id.profileFragment
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
For a detailed understanding of Navigation library please read this article
https://medium.com/android-news/beginners-guide-to-bottom-navigation-with-android-jetpack-5485d2b8bbb5
This question already has answers here:
android navigation component up button, arrow displays but popTo not working
(2 answers)
Closed 1 year ago.
I have an AppCompatActivity with a FragmentContainerView and a BottomNavigationView.
When I navigate from a fragment_A to the fragment_B(which are not in the bottom menu) a back button appears, the problem is that the back button closes the app instead of going back to the previous fragment_A.
My activity xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:liftOnScroll="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="#+id/homeToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways|snap" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nav_host_fragment_activity_home"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="#+id/nav_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/appBarLayout"
app:navGraph="#navigation/mobile_navigation" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom"
app:layout_behavior=".ui.behaviour.TestBehavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="#menu/bottom_nav_menu" />
</androidx.constraintlayout.widget.ConstraintLayout>
And this is the kotlin code:
class HomeActivity : AppCompatActivity() {
private lateinit var binding: ActivityHomeBinding
private val viewModel: HomeActivityViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHomeBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.homeToolbar)
val navView: BottomNavigationView = binding.navView
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment_activity_home) as NavHostFragment
val navController = navHostFragment.navController
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_home,
R.id.navigation_fragment_a,
R.id.navigation_notifications
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
}
This is the action xml:
<action
android:id="#+id/navigate_to_fragment_b"
app:destination="#id/fragment_b"
app:enterAnim="#anim/slide_in_right"
app:exitAnim="#anim/slide_out_left"
app:popEnterAnim="#anim/slide_in_right"
app:popExitAnim="#anim/slide_out_left" />
This the code I use to obtain the navController reference:
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment_activity_home) as NavHostFragment
val navController = navHostFragment.navController
And this is the navigation function that i call from the fragment_A:
navController.navigate(R.id.navigate_to_fragment_b)
I tried to override the onBackPressed() in my activity but it's never gets called.
Update
I have confused the back button with the up button.
In my case back button returns to previous fragment but up button close the app.
So my problem is with the UP BUTTON.
UP button appears only when I navigate to the fragment_B, and referring to this I would say that there is something that does not work because it closes the app for me.
The Up button never exits your app
If a user is at the app's start destination, then the Up button does not appear, because the Up button never exits the app. The Back button, however, is shown and does exit the app.
Thanks to ianhanniballake for the solution in this answer
// Java
#Override
public boolean onSupportNavigateUp() {
return Navigation.findNavController(this, R.id.main_fragment).navigateUp()|| super.onSupportNavigateUp();
}
// Kotlin
override fun onSupportNavigateUp(): Boolean {
return Navigation.findNavController(this, R.id.main_fragment).navigateUp() || super.onSupportNavigateUp()
}
In my application, I want to show bottom tabs and when click on these tabs show one fragment.
For this I used BottomNavigationView and NavigationUI component for show fragments
My XML code:
<fragment
android:id="#+id/homePage_fragmentNavHost"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/home_navigator"
app:defaultNavHost="true"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="#+id/homePage_bottomNavBar"
app:layout_constraintTop_toBottomOf="#+id/homePage_toolbar"/>
<!--Bottom menu-->
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/homePage_bottomNavBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:background="?android:attr/windowBackground"
app:menu="#menu/menu_home_navigation"
app:labelVisibilityMode="selected"
app:itemTextAppearanceActive="#style/BottomNavigationView.Active"
app:itemTextAppearanceInactive="#style/BottomNavigationView"
app:itemTextColor="#color/bottom_nav_bar_colors"
app:itemIconTint="#color/bottom_nav_bar_colors"/>
And I write below codes, for connect NavigationUi and BottomNavigationView :
private fun setupNavigation() {
val navController = Navigation.findNavController(this, R.id.homePage_fragmentNavHost)
NavigationUI.setupWithNavController(homePage_bottomNavBar, navController)
}
override fun onSupportNavigateUp() = Navigation.findNavController(this, R.id.homePage_fragmentNavHost).navigateUp()
But always show the item 0 for default tab.
I want write condition and check one value and with this value set default tab for this NavigationUi and BottomNavigationView.
How can I do it?
In the graph you can define the startDestination.
Something like:
val navHostFragment = supportFragmentManager.findFragmentById(R.id.homePage_fragmentNavHost) as NavHostFragment
val graphInflater = navHostFragment.navController.navInflater
val navGraph = graphInflater.inflate(R.navigation.home_navigator)
navGraph.startDestination = R.id.nav_xxxxx
navController.graph = navGraph
findViewById<BottomNavigationView>(R.id.homePage_bottomNavBar)
.setupWithNavController(navController)
I have installed the latest canary version of Android Studio, and followed this (https://developer.android.com/topic/libraries/architecture/navigation/navigation-implementing) instruction to implement a simple two page navigation. Basically page1 has a button, and when it is clicked, the app shows page2.
It works, but there is one problem... It does not seem to do anything with the action bar automatically. Is it supposed to show up/back arrow and the "Label" attribute on the action bar automatically by the navigation library? Or am I supposed to do all the work manually as before? I want to show the back arrow and "Details" on action(tool) bar when page2 is showing.
On button click at page 1.
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
{
button1.setOnClickListener {
val nav = NavHostFragment.findNavController(this);
nav.navigate(R.id.show_page2)
}
}
Main activity XML. By default it was the default Action Bar, I have replaced it with a ToolBar. There was no difference.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_height="?attr/actionBarSize"
android:elevation="4dp"
android:background="?attr/colorPrimary"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:theme="#style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
android:layout_width="match_parent">
</androidx.appcompat.widget.Toolbar>
<fragment
android:id="#+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/toolbar"
app:navGraph="#navigation/nav_graph"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Nav graph XML.
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/nav_graph"
app:startDestination="#id/page1">
<activity
android:id="#+id/mainActivity2"
android:name="com.android.navtest.MainActivity"
android:label="activity_main"
tools:layout="#layout/activity_main"/>
<fragment
android:id="#+id/page1"
android:name="com.android.navtest.BlankFragment2"
android:label="Home page"
tools:layout="#layout/page1">
<action
android:id="#+id/show_page2"
app:destination="#id/page2"
app:enterAnim="#anim/anim1"
app:popExitAnim="#anim/anim2"/>
</fragment>
<fragment
android:id="#+id/page2"
android:name="com.android.navtest.BlankFragment"
android:label="Details"
tools:layout="#layout/page2"/>
</navigation>
You can connect your ActionBar to a NavController using NavigationUI.setupActionBarWithNavController(). This is generally done in your Activity, right after you call setSupportActionBar():
supportActionBar = findViewById<Toolbar>(R.id.toolbar)
// Get the NavController for your NavHostFragment
val navController = findNavController(R.id.nav_host_fragment)
// Set up the ActionBar to stay in sync with the NavController
setupActionBarWithNavController(navController)
This approach is covered in the Navigation talk at Google I/O 2018.
If you want to have navigation back button hidden in more than one place (default is only for home fragment) you can add ids of fragments to AppBarConfiguration and pass this as second parameter of setupActionBarWithNavController, for example:
val appBarConfiguration = AppBarConfiguration(setOf(R.id.splashFragment, R.id.onboardingFragment, R.id.homeFragment))
setupActionBarWithNavController(findNavController(R.id.nav_host), appBarConfiguration)
This is what I have done.
onSupportNavigateUp is called when the user navigates up and it set again. by calling this setupActionBarWithNavController tell android to update the title of toolbar.
navigateUp Handles the Up button by delegating its behavior to the given NavController. This should generally be called from AppCompatActivity.onSupportNavigateUp().
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityGameConfigBinding =
DataBindingUtil.setContentView(this, R.layout.activity_game_config)
supportActionBar?.show()
val navController = Navigation.findNavController(this, R.id.myNavHostFragment)
NavigationUI.setupActionBarWithNavController(this, navController, null)
appBarConfiguration = AppBarConfiguration.Builder(navController.graph)
.build()
NavigationUI.setupWithNavController(binding.navView, navController)
}
override fun onSupportNavigateUp(): Boolean {
val navController = Navigation.findNavController(this, R.id.myNavHostFragment)
return NavigationUI.navigateUp(navController, appBarConfiguration)
}
my solution with binding - the code is in MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.main_activity)
navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
setSupportActionBar(toolbar)//needs to be after binding
toolbar.setupWithNavController(navController,AppBarConfiguration(navController.graph))
}
as for the titles - first I removed labels (android:label) from the fragments in navigation graph (label overwrites title from what I've tested)
<fragment
android:id="#+id/productListFragment"
android:name="com.example.ProductListFragment"
android:label="TO_BE_REMOVED"
tools:layout="#layout/product_list_fragment">
<action
android:id="#+id/action_productListFragment_to_mainMenuFragment"
app:destination="#id/mainMenuFragment" />
</fragment>
each fragment sets the title and subtitle in onResume, here example from ProductListFragment
override fun onResume() {
super.onResume()
val actionBar = (activity as AppCompatActivity).supportActionBar
actionBar?.title = getString(R.string.product_list_title)
actionBar?.subtitle = getString(R.string.product_list_subtitle)
}