I want to navigate between activities while coloring the current item on bottom navigation.
What is the best approach to achieve this?
Currently, on every activity when I'm using BottomNavigationView (in this casecom.google.android.material.bottomnavigation.BottomNavigationView),
I'm duplicating almost the same code for navigating between activities which is hard to maintain:
Marking the current itemId
Setting thesetOnNavigationItemSelectedListener without the current itemId.
jesta_bottom_navigation.selectedItemId = R.id.nav_do_jesta
jesta_bottom_navigation.setOnNavigationItemSelectedListener {
val intent = when (it.itemId) {
R.id.nav_ask_jesta -> {
Intent(this#DoJestaActivity, AskJestaActivity::class.java)
}
R.id.nav_status -> {
Intent(this#DoJestaActivity, StatusActivity::class.java)
}
// Settings Activity
else -> {
Intent(this#DoJestaActivity, SettingsActivity::class.java)
}
}
startActivity(intent)
true
}
frame_bottom_navigation_view.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/jesta_bottom_navigation"
style="#style/Widget.Jesta.BottomNavigationView"
android:layout_width="match_parent"
android:layout_height="#dimen/bottom_bar"
android:layout_gravity="bottom"
app:itemIconSize="30dp"
app:itemIconTint="#drawable/jesta_bottom_navigation_colors"
app:labelVisibilityMode="unlabeled"
app:menu="#menu/bottom_nav_drawer_menu" />
</FrameLayout>
What about using an external library for doing this faster
AHBottomNavigation
BottomNavigationViewEx
Related
when I switch between the fragment I created, the home fragment is not deleted, it writes on the others.
please i have an issue. I am using bottom navigation and nav controller. once I switch fragments, the home fragment keeps showing under the rest
`
class FeedActivity : AppCompatActivity() {
private lateinit var bottomNavigationView: BottomNavigationView
//loadFragment(HomeFragment())
bottomNavigationView = findViewById(R.id.bottom_navigation) as BottomNavigationView
bottomNavigationView.setOnItemSelectedListener {
when (it.itemId) {
R.id.nav_home -> {
loadFragment(HomeFragment())
true
}
R.id.nav_search -> {
loadFragment(SearchFragment())
true
}
R.id.nav_add -> {
loadFragment(AddFragment())
true
}
R.id.nav_notifications -> {
loadFragment(NotificationsFragment())
true
}
R.id.nav_profile -> {
loadFragment(ProfileFragment())
true
}
else -> throw IllegalStateException("Someone forgot to add enough fragment cases to 'when' clause!")
}
}
}
private fun loadFragment(fragment: Fragment){
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.cercevekapsayici,fragment)
transaction.commit()
}`
`<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.FeedActivity">
<FrameLayout
android:id="#+id/cercevekapsayici"
android:layout_above="#id/bottom_appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/bottom_appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true" >
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/windowBackground"
app:itemIconTint="#color/cardview_dark_background"
app:labelVisibilityMode="unlabeled"
app:menu="#menu/bottom_navigation" />
</com.google.android.material.appbar.AppBarLayout>
[[enter image description here](https://i.stack.imgur.com/3D2Z8.png)](https://i.stack.imgur.com/llwZ5.png)`
I want the recyclerview to disappear when I switch to other fragments.
You can make your FramLayout height match parent :
<FrameLayout
android:id="#+id/cercevekapsayici"
android:layout_above="#id/bottom_appbar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Your loadFragment function replaces whatever Fragment is attached to R.id.cercevekapsayici, which is a FrameLayout that contains your fragments. But your RecyclerView is part of your main layout, so it will always be there whatever you attach to R.id.cercevekapsayici.
If you want it to be replaced, put it in a Fragment and add it to that FrameLayout - then it'll be removed when you switch to a different fragment.
Maybe you need to give background to the other fragments
everyone. I have a BottomnNavigationsBar and a FrameLayout inside my activity_main.xml file. In the frame I display a ViewPager within a Fragment. The navigation contains 3 elements. If I click on one, the clicked status does not change. The respective fragment is displayed correctly, but the icon does not change in color and size.
What am I doing wrong or what do I have to change?
Here is my MainActivity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
setFragementTo(HomeFragment.newInstance("",""))
viewBinding.bottomNavigationView.setOnItemSelectedListener{
when(it.itemId)
{
R.id.home ->{
setFragementTo(HomeFragment.newInstance("",""))
}
R.id.neu ->{
setFragementTo(NewFragment.newInstance("",""))
}
R.id.settings ->{
setFragementTo(FilterFragment.newInstance("",""))
}
}
false
}
}
private fun setFragementTo(fragment: Fragment) {
supportFragmentManager.beginTransaction().replace(R.id.myFrameInMain,fragment).commit()
}
activity_main.xml:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<FrameLayout
android:id="#+id/myFrameInMain"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/bottomNavigationView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"></FrameLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:background="#drawable/bottom_nav_style"
app:itemRippleColor="#color/white"
app:itemTextColor="#color/item_bottom_nav"
app:itemIconTint="#color/item_bottom_nav"
android:id="#+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="#menu/bottom_nav"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Here I click on the middle icon in the navigation. The fragment appears correctly, but the icon in the navigation does not
And here the filter icon:
I think I got it. I had to change the parameter from false to true in the setOnItemSelectedListener method
I have my MainActivity setup which looks like this :
<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=".ui.host.HostActivity">
<fragment
android:id="#+id/mainNavigationFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/windowBackground"
app:labelVisibilityMode="labeled"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="#menu/main_nav_menu" />
</androidx.constraintlayout.widget.ConstraintLayout>
And the bottom navigation has 3 tabs which can only be shown after some initial authentication and setup user calls have been performed.
What is the best way to show a Loading/Setting up user screen before I let user start using the bottom nav view.
One way I am thinking is to delay the setup of NaHostFragment in my Activity until all the setup is done.
So basically toggle the visibility of mainNavigationFragment and BottomNavigationView till the setup is completed.
Are there any other ideas ?
The NavigationUI documentation actually uses hiding a BottomNavigationView as its example. You could also use an OnDestinationChangedListener to update the visibility of your BottomNavigationView, say, only hiding it while the user is on the login screen (note that as per the Conditional Navigation documentation, you shouldn't be doing this in the start destination of your graph, but redirecting users to those screens):
navController.addOnDestinationChangedListener { _, destination, _ ->
if(destination.parent?.id == R.id.login) {
bottomNavigationView.visibility = View.GONE
} else {
bottomNavigationView.visibility = View.VISIBLE
}
}
in my Activity
//using view model
loginViewModel.isLoggedIn.observe(this, Observer { loggedIn ->
if(!loggedIn) {
startActivity(Intent(this#MainActivity,LoginActivity::class.java))
finish()
}
else {
//set up the controller
val navController = findNavController(R.id.nav_host_fragment)
nav_view.setupWithNavController(navController)
navController.addOnDestinationChangedListener { controller, destination, arguments ->
when(destination.id) {
R.id.navigation_home,R.id.navigation_profile,R.id.navigation_account -> {
nav_view.visibility = View.VISIBLE
}
else -> {
nav_view.visibility = View.GONE
}
}
}
}
I study the google example at https://github.com/android/architecture-components-samples/tree/master/NavigationAdvancedSample .And it works like this :
But I need the about fragment to take the whole screen.What is best practice?
I have try this :
activity?.supportFragmentManager?.beginTransaction()?.replace(R.id.root_activity,Detail())?.addToBackStack("About")?.commit()
and get this:
the activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:id="#+id/root_activity"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.android.navigationadvancedsample.MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nav_host_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:menu="#menu/bottom_nav"/>
I can set activity?.findViewById<BottomNavigationView>(R.id.bottom_nav)?.visibility=View.GONE
or activity?.findViewById<BottomNavigationView>(R.id.bottom_nav)?.visibility=View.INVISIBLE
But when it comes back,it looks like this,I think this is not good way:
thanks!!
You have to toggle visibility of BottomNavigationView as shown below:
NavController.OnDestinationChangedListener destinationChangedListener = new NavController.OnDestinationChangedListener() {
#Override
public void onDestinationChanged(#NonNull NavController controller, #NonNull NavDestination destination, #Nullable Bundle arguments) {
if(destination.getId() == R.id.navigation_notifications){
navView.setVisibility(View.GONE);
}else{
navView.setVisibility(View.VISIBLE);
}
}
};
navController.addOnDestinationChangedListener(destinationChangedListener);
in case anybody has ths same question,I post my answer here.
to work around ,I put BottomNavigationView into a MainFragment instead of MainActivity . And in the MainActivity dynamically instantiate the MainFragment,so I can replace the MainFragment as a whole in my deep child fragment .
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val container = FrameLayout(this)
container.id = R.id.activity_container
setContentView(container)
supportFragmentManager.beginTransaction().replace(R.id.activity_container,MainFragment()).commit()
}
}
in the nested fragment :
activity?.supportFragmentManager?.beginTransaction()?.replace(R.id.activity_container,About())?.addToBackStack("Abouts")?.commit()
final effect(have strip away the toolbar):
I am using Navigation from Android Jetpack to navigate between screens.
Now I want to set startDestination dynamically.
I have an Activity named MainActivity
And two Fragments, FragmentA & FragmentB.
var isAllSetUp : Boolean = // It is dynamic and I’m getting this from Preferences.
If(isAllSetUp)
{
// show FragmentA
}
else
{
//show FragmentB
}
I want to set above flow using Navigation Architecture Component. Currently I have used startDestionation as below but it’s not fulfilling my requirement.
<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/lrf_navigation"
app:startDestination="#id/fragmentA">
<fragment
android:id="#+id/fragmentA"
android:name="com.mindinventory.FragmentA"
android:label="fragment_a"
tools:layout="#layout/fragment_a" />
</navigation>
Is it possible to set startDestination conditionally using Android Navigation Architecture Component?
Finally, I got a solution to my query...
Put below code in onCreate() method of Activity.
Kotlin code
val navHostFragment = (supportFragmentManager.findFragmentById(R.id.home_nav_fragment) as NavHostFragment)
val inflater = navHostFragment.navController.navInflater
val graph = inflater.inflate(R.navigation.nav_main)
//graph.addArgument("argument", NavArgument)
graph.setStartDestination(R.id.fragment1)
//or
//graph.setStartDestination(R.id.fragment2)
navHostFragment.navController.graph = graph
Java code
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.home_nav_fragment); // Hostfragment
NavInflater inflater = navHostFragment.getNavController().getNavInflater();
NavGraph graph = inflater.inflate(R.navigation.nav_main);
//graph.addArgument("argument", NavArgument)
graph.setStartDestination(R.id.fragment1);
navHostFragment.getNavController().setGraph(graph);
navHostFragment.getNavController().getGraph().setDefaultArguments(getIntent().getExtras());
NavigationView navigationView = findViewById(R.id.navigationView);
NavigationUI.setupWithNavController(navigationView, navHostFragment.getNavController());
Additional Info
As #artnest suggested, remove the app:navGraph attribute from the layout. It would look something like this after removal
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/home_nav_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true" />
</FrameLayout>
In the case of a fragment tag used instead of FragmentContainerView, the above changes remain the same
Some of the APIs have changed, are unavailable or are not necessary since Akash's answer. It's a bit simpler now.
MainActivity.java:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NavHostFragment navHost = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_main_nav_host);
NavController navController = navHost.getNavController();
NavInflater navInflater = navController.getNavInflater();
NavGraph graph = navInflater.inflate(R.navigation.navigation_main);
if (false) {
graph.setStartDestination(R.id.oneFragment);
} else {
graph.setStartDestination(R.id.twoFragment);
}
navController.setGraph(graph);
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!-- Following line omitted inside <fragment> -->
<!-- app:navGraph="#navigation/navigation_main" -->
<fragment
android:id="#+id/fragment_main_nav_host"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
navigation_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<!-- Following line omitted inside <navigation>-->
<!-- app:startDestination="#id/oneFragment" -->
<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/navigation_main"
>
<fragment
android:id="#+id/oneFragment"
android:name="com.apponymous.apponymous.OneFragment"
android:label="fragment_one"
tools:layout="#layout/fragment_one"/>
<fragment
android:id="#+id/twoFragment"
android:name="com.apponymous.apponymous.TwoFragment"
android:label="fragment_two"
tools:layout="#layout/fragment_two"/>
</navigation>
This can be done with navigation action. Because fragmentA is your start destination, so define an action in fragmentA.
Note these two fields:
app:popUpToInclusive="true" app:popUpTo="#id/fragmentA"
<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/lrf_navigation"
app:startDestination="#id/fragmentA">
<fragment
android:id="#+id/fragmentA"
android:name="com.mindinventory.FragmentA"
android:label="fragment_a"
tools:layout="#layout/fragment_a">
<action android:id="#+id/action_a_to_b"
app:destination="#id/fragmentB"
app:popUpToInclusive="true"
app:popUpTo="#id/fragmentA"/>
<fragment>
<fragment
android:id="#+id/fragmentB"
android:name="com.mindinventory.FragmentB"
android:label="fragment_b"
tools:layout="#layout/fragment_b"/>
</navigation>
When your MainActivity started, just do the navigation with action id, it will remove fragmentA in the stack, and jump to fragmentB. Seemingly, fragmentB is your start destination.
if(!isAllSetUp)
{
// FragmentB
navController.navigate(R.id.action_a_to_b)
}
this is not an answer but Just a replication of #Akash Patel answer in more clean and clear way
// in your MainActivity
navController = findNavController(R.id.nav_host_fragment)
val graph = navController.navInflater.inflate(R.navigation.nav_graph)
if (Authentication.checkUserLoggedIn()) {
graph.startDestination = R.id.homeFragment
} else {
graph.startDestination = R.id.loginFragment
}
navController.graph = graph
You can set your starting destination programmatically, however, most of the time your starting logic will consult some remote endpoint. If you don't show anything on screen your UI will look bad.
What I do is always show a Splash screen. It will determine which is the next Screen to Show.
For instance, in the picture above you can ask in the Splash Screen State if there is a saved LoginToken. In case it's empty then you navigate to the Login Screen.
Once the Login Screen is done, then you analyze the result save the Token and navigate to your Next Fragment Home Screen.
When the Back Button is Pressed in the Home Screen, you will send back a Result message to the Splash Screen that indicates it to finish the App.
To pop 1 Fragment back and navigate to another you can use the following code:
val nextDestination = if (loginSuccess) {
R.id.action_Dashboard
} else {
R.id.action_NotAuthorized
}
val options = NavOptions.Builder()
.setPopUpTo(R.id.loginParentFragment, true)
.build()
findNavController().navigate(nextDestination, null, options)