Custom back icon in android toolbar using navigation component, bottomnavmenu - android

I want to use my own back icon in toolbar while using navigation component and bottom nav menu.
I have tried every possible solution but still I am displayed default back icon.
When I write
binding.toolbar.setNavigationIcon(R.drawable.ic_arrow_back)
in onCreate MainActivity then that icon is displayed but when I navigate to next screen it uses default.
I added onDestinationListener
val destinationChangedListener =
NavController.OnDestinationChangedListener { controller, destination, arguments ->
when(destination.id){
R.id.subCategoriesFragment -> {
// Nothing is working
supportActionBar?.setHomeButtonEnabled(true)
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_arrow_back)
binding.toolbar.setNavigationIcon(R.drawable.ic_arrow_back)
}
else -> {
binding.toolbar.navigationIcon = null
}
}
}
Still toolbar displays default icon.
I am using google's Navigation extention which manages backstacks of all bottom menus.
Extension code is below.
/**
* Manages the various graphs needed for a [BottomNavigationView].
*
* This sample is a workaround until the Navigation Component supports multiple back stacks.
*/
fun BottomNavigationView.setupWithNavController(
navGraphIds: List<Int>,
fragmentManager: FragmentManager,
containerId: Int,
intent: Intent
): LiveData<NavController> {
// Map of tags
val graphIdToTagMap = SparseArray<String>()
// Result. Mutable live data with the selected controlled
val selectedNavController = MutableLiveData<NavController>()
var firstFragmentGraphId = 0
// First create a NavHostFragment for each NavGraph ID
navGraphIds.forEachIndexed { index, navGraphId ->
val fragmentTag = getFragmentTag(index)
// Find or create the Navigation host fragment
val navHostFragment = obtainNavHostFragment(
fragmentManager,
fragmentTag,
navGraphId,
containerId
)
// Obtain its id
val graphId = navHostFragment.navController.graph.id
if (index == 0) {
firstFragmentGraphId = graphId
}
// Save to the map
graphIdToTagMap[graphId] = fragmentTag
// Attach or detach nav host fragment depending on whether it's the selected item.
if (this.selectedItemId == graphId) {
// Update livedata with the selected graph
selectedNavController.value = navHostFragment.navController
attachNavHostFragment(fragmentManager, navHostFragment, index == 0)
} else {
detachNavHostFragment(fragmentManager, navHostFragment)
}
}
// Now connect selecting an item with swapping Fragments
var selectedItemTag = graphIdToTagMap[this.selectedItemId]
val firstFragmentTag = graphIdToTagMap[firstFragmentGraphId]
var isOnFirstFragment = selectedItemTag == firstFragmentTag
// When a navigation item is selected
setOnNavigationItemSelectedListener { item ->
LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(SELECTED_RESELETED_ITEM).apply {
putExtra("item", item.itemId)
})
// Don't do anything if the state is state has already been saved.
if (fragmentManager.isStateSaved) {
false
} else {
val newlySelectedItemTag = graphIdToTagMap[item.itemId]
if (selectedItemTag != newlySelectedItemTag) {
// Pop everything above the first fragment (the "fixed start destination")
fragmentManager.popBackStack(firstFragmentTag,
FragmentManager.POP_BACK_STACK_INCLUSIVE)
val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag)
as NavHostFragment
// Exclude the first fragment tag because it's always in the back stack.
if (firstFragmentTag != newlySelectedItemTag) {
// Commit a transaction that cleans the back stack and adds the first fragment
// to it, creating the fixed started destination.
fragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.nav_default_enter_anim,
R.anim.nav_default_exit_anim,
R.anim.nav_default_pop_enter_anim,
R.anim.nav_default_pop_exit_anim)
.attach(selectedFragment)
.setPrimaryNavigationFragment(selectedFragment)
.apply {
// Detach all other Fragments
graphIdToTagMap.forEach { _, fragmentTagIter ->
if (fragmentTagIter != newlySelectedItemTag) {
detach(fragmentManager.findFragmentByTag(firstFragmentTag)!!)
}
}
}
.addToBackStack(firstFragmentTag)
.setReorderingAllowed(true)
.commit()
}
selectedItemTag = newlySelectedItemTag
isOnFirstFragment = selectedItemTag == firstFragmentTag
selectedNavController.value = selectedFragment.navController
true
} else {
false
}
}
}
// Optional: on item reselected, pop back stack to the destination of the graph
setupItemReselected(graphIdToTagMap, fragmentManager)
// Handle deep link
setupDeepLinks(navGraphIds, fragmentManager, containerId, intent)
// Finally, ensure that we update our BottomNavigationView when the back stack changes
fragmentManager.addOnBackStackChangedListener {
if (!isOnFirstFragment && !fragmentManager.isOnBackStack(firstFragmentTag)) {
this.selectedItemId = firstFragmentGraphId
}
// Reset the graph if the currentDestination is not valid (happens when the back
// stack is popped after using the back button).
selectedNavController.value?.let { controller ->
if (controller.currentDestination == null) {
controller.navigate(controller.graph.id)
}
}
}
return selectedNavController
}
private fun BottomNavigationView.setupDeepLinks(
navGraphIds: List<Int>,
fragmentManager: FragmentManager,
containerId: Int,
intent: Intent
) {
navGraphIds.forEachIndexed { index, navGraphId ->
val fragmentTag = getFragmentTag(index)
// Find or create the Navigation host fragment
val navHostFragment = obtainNavHostFragment(
fragmentManager,
fragmentTag,
navGraphId,
containerId
)
// Handle Intent
if (navHostFragment.navController.handleDeepLink(intent)
&& selectedItemId != navHostFragment.navController.graph.id) {
this.selectedItemId = navHostFragment.navController.graph.id
}
}
}
private fun BottomNavigationView.setupItemReselected(
graphIdToTagMap: SparseArray<String>,
fragmentManager: FragmentManager
) {
setOnNavigationItemReselectedListener { item ->
LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(SELECTED_RESELETED_ITEM).apply {
putExtra("item", item.itemId)
})
val newlySelectedItemTag = graphIdToTagMap[item.itemId]
val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag)
as NavHostFragment
val navController = selectedFragment.navController
// Pop the back stack to the start destination of the current navController graph
navController.popBackStack(
navController.graph.startDestination, false
)
}
}
private fun detachNavHostFragment(
fragmentManager: FragmentManager,
navHostFragment: NavHostFragment
) {
fragmentManager.beginTransaction()
.detach(navHostFragment)
.commitNow()
}
private fun attachNavHostFragment(
fragmentManager: FragmentManager,
navHostFragment: NavHostFragment,
isPrimaryNavFragment: Boolean
) {
fragmentManager.beginTransaction()
.attach(navHostFragment)
.apply {
if (isPrimaryNavFragment) {
setPrimaryNavigationFragment(navHostFragment)
}
}
.commitNow()
}
private fun obtainNavHostFragment(
fragmentManager: FragmentManager,
fragmentTag: String,
navGraphId: Int,
containerId: Int
): NavHostFragment {
// If the Nav Host fragment exists, return it
val existingFragment = fragmentManager.findFragmentByTag(fragmentTag) as NavHostFragment?
existingFragment?.let { return it }
// Otherwise, create it and return it.
val navHostFragment = NavHostFragment.create(navGraphId)
fragmentManager.beginTransaction()
.add(containerId, navHostFragment, fragmentTag)
.commitNow()
return navHostFragment
}
private fun FragmentManager.isOnBackStack(backStackName: String): Boolean {
val backStackCount = backStackEntryCount
for (index in 0 until backStackCount) {
if (getBackStackEntryAt(index).name == backStackName) {
return true
}
}
return false
}
private fun getFragmentTag(index: Int) = "bottomNavigation#$index"
Can anyone please help, how I can use my own back icon.

The developers of the navigation component didn't provide a way to change the icon.
But this is possible by next way:
Create a package in your project: "androidx.navigation.ui"
Create ModifiedAbstractAppBarOnDestinationChangedListener:
package androidx.navigation.ui;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.graphics.drawable.DrawerArrowDrawable;
import androidx.customview.widget.Openable;
import androidx.navigation.FloatingWindow;
import androidx.navigation.NavController;
import androidx.navigation.NavDestination;
import java.lang.ref.WeakReference;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* The abstract OnDestinationChangedListener for keeping any type of app bar updated.
* This handles both updating the title and updating the Up Indicator, transitioning between
* the drawer icon and up arrow as needed.
* #hide
*/
public abstract class ModifiedAbstractAppBarOnDestinationChangedListener
implements NavController.OnDestinationChangedListener {
private final Context mContext;
private final Set<Integer> mTopLevelDestinations;
#Nullable
private final WeakReference<Openable> mOpenableLayoutWeakReference;
private DrawerArrowDrawable mArrowDrawable;
private ValueAnimator mAnimator;
ModifiedAbstractAppBarOnDestinationChangedListener(#NonNull Context context,
#NonNull AppBarConfiguration configuration) {
mContext = context;
mTopLevelDestinations = configuration.getTopLevelDestinations();
Openable openableLayout = configuration.getOpenableLayout();
if (openableLayout != null) {
mOpenableLayoutWeakReference = new WeakReference<>(openableLayout);
} else {
mOpenableLayoutWeakReference = null;
}
}
protected abstract void setTitle(CharSequence title);
protected abstract void setNavigationIcon(Drawable icon, #StringRes int contentDescription, int destinationId);
#Override
public void onDestinationChanged(#NonNull NavController controller,
#NonNull NavDestination destination, #Nullable Bundle arguments) {
if (destination instanceof FloatingWindow) {
return;
}
Openable openableLayout = mOpenableLayoutWeakReference != null
? mOpenableLayoutWeakReference.get()
: null;
if (mOpenableLayoutWeakReference != null && openableLayout == null) {
controller.removeOnDestinationChangedListener(this);
return;
}
int id = destination.getId();
CharSequence label = destination.getLabel();
if (label != null) {
// Fill in the data pattern with the args to build a valid URI
StringBuffer title = new StringBuffer();
Pattern fillInPattern = Pattern.compile("\\{(.+?)\\}");
Matcher matcher = fillInPattern.matcher(label);
while (matcher.find()) {
String argName = matcher.group(1);
if (arguments != null && arguments.containsKey(argName)) {
matcher.appendReplacement(title, "");
//noinspection ConstantConditions
title.append(arguments.get(argName).toString());
} else {
throw new IllegalArgumentException("Could not find " + argName + " in "
+ arguments + " to fill label " + label);
}
}
matcher.appendTail(title);
setTitle(title);
}
boolean isTopLevelDestination = NavigationUI.matchDestinations(destination,
mTopLevelDestinations);
if (openableLayout == null && isTopLevelDestination) {
setNavigationIcon(null, 0, id);
} else {
setActionBarUpIndicator(openableLayout != null && isTopLevelDestination, id);
}
}
private void setActionBarUpIndicator(boolean showAsDrawerIndicator, int destinationId) {
boolean animate = true;
if (mArrowDrawable == null) {
mArrowDrawable = new DrawerArrowDrawable(mContext);
// We're setting the initial state, so skip the animation
animate = false;
}
setNavigationIcon(mArrowDrawable, showAsDrawerIndicator
? R.string.nav_app_bar_open_drawer_description
: R.string.nav_app_bar_navigate_up_description,
destinationId);
float endValue = showAsDrawerIndicator ? 0f : 1f;
if (animate) {
float startValue = mArrowDrawable.getProgress();
if (mAnimator != null) {
mAnimator.cancel();
}
mAnimator = ObjectAnimator.ofFloat(mArrowDrawable, "progress",
startValue, endValue);
mAnimator.start();
} else {
mArrowDrawable.setProgress(endValue);
}
}
}
Create ModifiedToolbarOnDestinationChangedListener and add ability to change or replace the drawable icon with getModifiedIcon(drawable, destinationId) method:
package androidx.navigation.ui;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.widget.Toolbar;
import androidx.navigation.NavController;
import androidx.navigation.NavDestination;
import androidx.transition.TransitionManager;
import java.lang.ref.WeakReference;
/**
* The OnDestinationChangedListener specifically for keeping a Toolbar updated.
* This handles both updating the title and updating the Up Indicator, transitioning between
* the drawer icon and up arrow as needed.
* #hide
*/
public class ModifiedToolbarOnDestinationChangedListener extends
ModifiedAbstractAppBarOnDestinationChangedListener {
private final WeakReference<Toolbar> mToolbarWeakReference;
public ModifiedToolbarOnDestinationChangedListener(
#NonNull Toolbar toolbar, #NonNull AppBarConfiguration configuration) {
super(toolbar.getContext(), configuration);
mToolbarWeakReference = new WeakReference<>(toolbar);
}
#Override
public void onDestinationChanged(#NonNull NavController controller,
#NonNull NavDestination destination, #Nullable Bundle arguments) {
Toolbar toolbar = mToolbarWeakReference.get();
if (toolbar == null) {
controller.removeOnDestinationChangedListener(this);
return;
}
super.onDestinationChanged(controller, destination, arguments);
}
#Override
protected void setTitle(CharSequence title) {
mToolbarWeakReference.get().setTitle(title);
}
#Override
protected void setNavigationIcon(Drawable icon, #StringRes int contentDescription, int destinationId) {
Toolbar toolbar = mToolbarWeakReference.get();
if (toolbar != null) {
Drawable modifiedIcon = getModifiedIcon(icon, destinationId);
boolean useTransition = modifiedIcon == null && toolbar.getNavigationIcon() != null;
toolbar.setNavigationIcon(modifiedIcon);
toolbar.setNavigationContentDescription(contentDescription);
if (useTransition) {
TransitionManager.beginDelayedTransition(toolbar);
}
}
}
#Nullable
protected Drawable getModifiedIcon(#Nullable Drawable drawable, int destinationId) {
return drawable;
}
}
Setup the Navigation component with modified classes and change the drawable icon in depend on destinationId:
fun setupWithNavController(toolbar: Toolbar, navController: NavController, configuration: AppBarConfiguration) {
val listener = object : ModifiedToolbarOnDestinationChangedListener(toolbar, configuration) {
override fun getModifiedIcon(drawable: Drawable?, destinationId: Int): Drawable? {
// Return the drawable in depend on destinationId
if (drawable is DrawerArrowDrawable){
drawable.color = Icon.getInstance().iconsColor
}
return drawable
}
}
navController.addOnDestinationChangedListener(listener)
toolbar.setNavigationOnClickListener { NavigationUI.navigateUp(navController, configuration) }
}

Related

setOnNavigationItemSelectedListener doesn't seem to work for me

It was working perfectly, I don't know where I messed up...
I have 5 items in my bottom navigation menu and it is always marked/set/highlighted/selected on the home activity (0), whenever I click on let's say ProfileActivity (4), it set it as selected just for a second and then selection goes back to the home activity (0), but the screen remains on the activity which I wanted.
Here is the bottom navigation:
AppBottomNavigation.kt
class AppBottomNavigation(private val bnv: BottomNavigationViewEx,
private val navNumber: Int,
activity: Activity): LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume() {
bnv.menu.getItem(navNumber).isChecked = true
}
init {
bnv.setIconSize(29f, 29f)
bnv.setTextVisibility(false)
bnv.enableItemShiftingMode(false)
bnv.enableShiftingMode(false)
bnv.enableAnimation(false)
for (i in 0 until bnv.menu.size()) {
bnv.setIconTintList(i, null)
}
bnv.setOnNavigationItemSelectedListener {
val nextActivity =
when (it.itemId) {
R.id.nav_item_home -> MainActivity::class.java
R.id.nav_item_search -> SearchActivity::class.java
R.id.nav_item_share -> ShareActivity::class.java
R.id.nav_item_likes -> LikesActivity::class.java
R.id.nav_item_profile -> ProfileActivity::class.java
else -> {
Log.e(BaseActivity.TAG, "unknown nav item clicked $it")
null
}
}
if (nextActivity != null) {
val intent = Intent(activity, nextActivity)
intent.flags = Intent.FLAG_ACTIVITY_NO_ANIMATION
activity.startActivity(intent)
activity.overridePendingTransition(0, 0)
true
} else {
false
}
}
}
}
fun BaseActivity.setupBottomNavigation(navNumber: Int){
AppBottomNavigation(bottom_navigation_view, navNumber,this)
}
here is a MainActivity.kt just as an example of how I called bottom navigation in activities:
class MainActivity : BaseActivity(), FeedAdapter.Listener {
private lateinit var mAdapter: FeedAdapter
private val TAG = "MainActivity"
private lateinit var mFirebase: FirebaseHelper
private var mLikesListeners: Map<String, ValueEventListener> = emptyMap()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
Log.d(TAG, "onCreate")
setupBottomNavigation(0)
First create BottomNavigationViewHolder class like
public class BottomNavigationViewHolder {
public static void setupBottomNavigationView(BottomNavigationViewEx bottomNavigationViewEx) {
bottomNavigationViewEx.enableAnimation(false);
bottomNavigationViewEx.enableItemShiftingMode(false);
bottomNavigationViewEx.enableShiftingMode(false);
}
public static void enableNavigation(final Context context, BottomNavigationView view) {
view.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
switch(menuItem.getItemId()) {
case R.id.activity_0:
Intent intent0 = new Intent(context, PostsActivity.class);
intent0.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(intent0);
break;
case R.id.activity_1:
Intent intent1 = new Intent(context, GuessHomeActivity.class);
context.startActivity(intent1);
break;
case R.id.activity_2:
Intent intent4 = new Intent(context, SendReceivedActivity.class);
context.startActivity(intent4);
break;
case R.id.activity_3:
Intent intent3 = new Intent(context, ProfileActivity.class);
context.startActivity(intent3);
break;
}
return true;
}
});
}
}
Then setup this navigation every activity
And number your activities like private Static final int ACTIVITY_NUM = 0 in first activity and so on up to last activity
private void setupBottomNavigationView() {
BottomNavigationViewEx bottomNavigationView = (BottomNavigationViewEx) findViewById(R.id.bottomNavViewBar);
BottomNavigationViewHolder.setupBottomNavigationView(bottomNavigationView);
BottomNavigationViewHolder.enableNavigation(this,bottomNavigationView);
Menu menu = bottomNavigationView.getMenu();
MenuItem menuItem = menu.getItem(ACTIVITY_NUM);
menuItem.setChecked(true);
}

Bottom Navigation & Fragment Recreation

I have a bottom navigation which runs on my Main Activity
private void BottomNavigation_NavigationItemSelected(object sender, BottomNavigationView.NavigationItemSelectedEventArgs e)
{
LoadFragment(e.Item.ItemId);
}
void LoadFragment(int id)
{
Android.Support.V4.App.Fragment fragment = null;
switch (id)
{
case Resource.Id.navigation_1:
fragment = Fragment1.NewInstance();
break;
case Resource.Id.navigation_2:
fragment = Fragment2.NewInstance();
break;
case Resource.Id.navigation_3:
fragment = Fragment3.NewInstance();
break;
}
if (fragment == null)
return;
SupportFragmentManager.BeginTransaction()
.Replace(Resource.Id.content_frame, fragment)
.Commit();
}
In my Fragment1 (which is Resource.Id.navigation_1) I am refreshing data
public async override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
RefreshItems(false);
}
The issue is, every time I click on navigation_1 for fragment1, the data gets refreshed.
I don't want to load the fragment/data again once it's already loaded.
Is it possible in Xamarin?
You can check if current fragment also executed Here is a function
public Fragment find(String identifier) {
Fragment fragment = (Fragment) getSupportFragmentManager().findFragmentByTag(identifier);
return fragment;
}
and in BottomNavigationView you must check like this
if (find(fragment.getIdentifier()) != null) {
return;
}
I'd this problem sometime ago, however I was developing on native kotlin code, here goes the code I've used to handle this:
class Navigation(val supportFragmentManager: FragmentManager,
val filterIcon: ImageView ): BottomNavigationView.OnNavigationItemSelectedListener {
override fun onNavigationItemSelected(item: MenuItem): Boolean {
when(item.itemId){
R.id.menu_bottom_home ->{
changeFragment(HomeFragment(), Constants.FRAG_HOME_FRAGMENT)
filterIcon.visibility = View.VISIBLE
}
R.id.menu_bottom_favorites ->{
changeFragment(FavoritesFragment(), Constants.FRAG_FAVORITES_FRAGMENT)
filterIcon.visibility = View.INVISIBLE
}
R.id.menu_bottom_reservations ->{
changeFragment(ReservationFragment(), Constants.FRAG_RESERVATION_FRAGMENT)
filterIcon.visibility = View.INVISIBLE
}
R.id.menu_bottom_profile ->{
changeFragment(ProfileFragment(), Constants.FRAG_PROFILE_FRAGMENT)
filterIcon.visibility = View.INVISIBLE
}
}
return true
}
fun changeFragment(fragment: Fragment, tag : String){
var contains: Fragment? = null
var current: Fragment? = null
if(supportFragmentManager.fragments != null && supportFragmentManager.fragments.isNotEmpty()) {
contains = supportFragmentManager.fragments.firstOrNull { x -> x.tag == tag }
current = supportFragmentManager.fragments.first { x -> x.isVisible }
}
if(current != null){
supportFragmentManager.beginTransaction()
.hide(current).commit()
}
if(contains != null){
supportFragmentManager.beginTransaction()
.show(contains).commit()
}
else{
supportFragmentManager.beginTransaction()
.add(R.id.fl_fragment_container, fragment, tag)
.addToBackStack(tag)
.commit()
}
}
}
Then you just have to set setOnNavigationItemSelectedListener with your Navigation class like this:
bnv_navigation.setOnNavigationItemSelectedListener(Navigation(supportFragmentManager, iv_filter_bar))

How to set default BottomNavigationView tab in Kotlin?

I have been trying to set my default selected tab using the widely accepted java method in my onCreate (bottomNavigationView.setSelectedItemId(R.id.item_id)) but this does not seem to work in Kotlin. Also, the fragments that are set to load on each selected item seem to load every time that menu item is selected, as well as drawing their contents over the previous fragment.
How can I set the default selected tab for my BottomNavigationView? Also am I correctly loading fragments or is my code not properly disposing of fragments before loading another one?
MainActivity.kt
class MainActivity : AppCompatActivity() {
private val SELECTED_ITEM = "arg_selected_item"
private var mBottomNav: BottomNavigationView? = null
private var mSelectedItem: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mBottomNav = findViewById(R.id.navigation) as BottomNavigationView
mBottomNav!!.setOnNavigationItemSelectedListener { item ->
selectFragment(item)
true
}
val selectedItem: MenuItem
if (savedInstanceState != null) {
mSelectedItem = savedInstanceState.getInt(SELECTED_ITEM, 0)
selectedItem = mBottomNav!!.menu.findItem(mSelectedItem)
} else {
selectedItem = mBottomNav!!.menu.getItem(0)
}
selectFragment(selectedItem)
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putInt(SELECTED_ITEM, mSelectedItem)
super.onSaveInstanceState(outState)
}
override fun onBackPressed() {
val homeItem = mBottomNav!!.menu.getItem(0)
if (mSelectedItem != homeItem.itemId) {
// select home item
selectFragment(homeItem)
} else {
super.onBackPressed()
}
}
private fun selectFragment(item: MenuItem) {
var frag: Fragment? = null
// init corresponding fragment
when (item.itemId) {
R.id.navigation_enrollments -> frag = EnrollmentsFragment.newInstance()
R.id.navigation_timeline -> frag = TimelineFragment.newInstance()
R.id.navigation_home -> frag = HomeFragment.newInstance()
R.id.navigation_alerts -> frag = AlertsFragment.newInstance()
R.id.navigation_profile -> frag = ProfileFragment.newInstance()
}
// update selected item
mSelectedItem = item.itemId
// uncheck the other items.
for (i in 0..mBottomNav!!.menu.size() - 1) {
val menuItem = mBottomNav!!.menu.getItem(i)
menuItem.isChecked = menuItem.itemId == item.itemId
}
updateToolbarText(item.title)
if (frag != null) {
val ft = supportFragmentManager.beginTransaction()
ft.add(R.id.content, frag, frag.tag)
ft.commit()
}
}
private fun updateToolbarText(text: CharSequence) {
val actionBar = supportActionBar
if (actionBar != null) {
actionBar.title = text
}
}
private fun getColorFromRes(#ColorRes resId: Int): Int {
return ContextCompat.getColor(this, resId)
}
}
Resolved the default selected tab issue by adding the following code to my onCreate function:
val bottomNavigationView: BottomNavigationView = findViewById(R.id.navigation) as BottomNavigationView
bottomNavigationView.selectedItemId = R.id.navigation_home
Also, I resolved the issue of fragments being drawn on top of one another by modifying the following:
if (frag != null) {
val ft = supportFragmentManager.beginTransaction()
ft.replace(R.id.content, frag, frag.tag)
ft.commit()
}
}
I changed ft.add to ft.replace

Fragment re-created on bottom navigation view item selected

Following is my code for bottom navigation view item selected
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment fragment = null;
switch (item.getItemId()) {
case R.id.action_one:
// Switch to page one
fragment = FragmentA.newInstance();
break;
case R.id.action_two:
// Switch to page two
fragment = FragmentB.newInstance();
break;
case R.id.action_three:
// Switch to page three
fragment = FragmentC.newInstance();
break;
}
getSupportFragmentManager().beginTransaction().replace(R.id.container,fragment,"TAG").commit();
return true;
}
});
Now my problem is every time fragment is re-created and don't want fragment to be recreated every time I also tried adding addToBackStack(null) but it this case on back button press keeps popping fragments from stack which I don't want.
Is there any way to display fragments on bottom navigation bar selected without re-creating fragment
With support library v26 you can do this
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
Fragment curFrag = mFragmentManager.getPrimaryNavigationFragment();
if (curFrag != null) {
fragmentTransaction.detach(curFrag);
}
Fragment fragment = mFragmentManager.findFragmentByTag(tag);
if (fragment == null) {
fragment = new YourFragment();
fragmentTransaction.add(container.getId(), fragment, tag);
} else {
fragmentTransaction.attach(fragment);
}
fragmentTransaction.setPrimaryNavigationFragment(fragment);
fragmentTransaction.setReorderingAllowed(true);
fragmentTransaction.commitNowAllowingStateLoss();
I faced the same problem and finally i i found the solution, you can try this code. it's work for me.
import android.support.annotation.NonNull;
import android.support.design.widget.BottomNavigationView;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MenuItem;
import android.widget.FrameLayout;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
public BottomNavigationView bv;
public home_fragment home;
public account_fragment afrag;
public other_fragment other;
public FrameLayout fr;
android.support.v4.app.Fragment current;
//public FragmentTransaction frt;
public static int temp=0;
final Fragment fragment11 = new account_fragment();
final Fragment fragment22 = new home_fragment();
final Fragment fragment33 = new other_fragment();
final FragmentManager fm = getSupportFragmentManager();
Fragment active = fragment11;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bv=(BottomNavigationView) findViewById(R.id.navigationView);
fm.beginTransaction().add(R.id.main_frame, fragment33, "3").hide(fragment33).commit();
fm.beginTransaction().add(R.id.main_frame, fragment22, "2").hide(fragment22).commit();
fm.beginTransaction().add(R.id.main_frame,fragment11, "1").commit();
bv.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.account:
fm.beginTransaction().hide(active).show(fragment11).commit();
active = fragment11;
return true;
case R.id.home1:
fm.beginTransaction().hide(active).show(fragment22).commit();
active = fragment22;
return true;
case R.id.other:
fm.beginTransaction().hide(active).show(fragment33).commit();
active = fragment33;
return true;
}
return false;
}
});
bv.setOnNavigationItemReselectedListener(new BottomNavigationView.OnNavigationItemReselectedListener() {
#Override
public void onNavigationItemReselected(#NonNull MenuItem item) {
Toast.makeText(MainActivity.this, "Reselected", Toast.LENGTH_SHORT).show();
}
});
}
}
This seemed to work well for me. Instead of attaching and detaching, i use show or hide to maintain fragment state.
public void changeFragment(Fragment fragment, String tagFragmentName) {
FragmentManager mFragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
Fragment currentFragment = mFragmentManager.getPrimaryNavigationFragment();
if (currentFragment != null) {
fragmentTransaction.hide(currentFragment);
}
Fragment fragmentTemp = mFragmentManager.findFragmentByTag(tagFragmentName);
if (fragmentTemp == null) {
fragmentTemp = fragment;
fragmentTransaction.add(R.id.frame_layout, fragmentTemp, tagFragmentName);
} else {
fragmentTransaction.show(fragmentTemp);
}
fragmentTransaction.setPrimaryNavigationFragment(fragmentTemp);
fragmentTransaction.setReorderingAllowed(true);
fragmentTransaction.commitNowAllowingStateLoss();
}
And this is how i use it
private void initViews() {
BottomNavigationView bottomNavigationView = findViewById(R.id.navigation);
bottomNavigationView.setOnNavigationItemSelectedListener
(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment selectedFragment = null;
switch (item.getItemId()) {
case R.id.explore:
changeFragment(new ExploreFragment(), ExploreFragment.class
.getSimpleName());
toggleViews(true, "");
break;
case R.id.favorite:
changeFragment(new FavoriteFragment(), FavoriteFragment.class
.getSimpleName());
toggleViews(false, "Favorites");
break;
case R.id.venue:
changeFragment(new VenueFragment(), VenueFragment.class.getSimpleName());
toggleViews(false, "Venues");
break;
case R.id.profile:
changeFragment(new ProfileFragment(), ProfileFragment.class
.getSimpleName());
toggleViews(false, "Profile");
break;
}
return true;
}
});
//Manually displaying the first fragment - one time only
changeFragment(new ExploreFragment(), ExploreFragment.class
.getSimpleName());
}
This solution stops a fragment from being re-created when the currently selected nav button is clicked again. However, a new fragment will be created every time the user clicks a different nav button.
Just add this line to avoid re-created Fragment from BottomNavigationView
bottomNavigation.setOnNavigationItemReselectedListener(new BottomNavigationView.OnNavigationItemReselectedListener() {
#Override
public void onNavigationItemReselected(#NonNull MenuItem item) {
// do nothing here
}
});
Or with a lambda:
bottomNavigation.setOnNavigationItemReselectedListener(item -> { });
Be careful when using replace. Even if providing a fragment that already exists in memory, replace will restart the fragment's lifecycle. To avoid a restart, the transaction object's methods includes add, show, and hide, which can be used to show the correct fragment without restarting it.
private fun switchFragment(selectedTabIndex: Int) {
val previousTabIndex = this.currentTabIndex
this.currentTabIndex = selectedTabIndex
val transaction = supportFragmentManager.beginTransaction()
val tag = fragments[this.currentTabIndex].tag
// if the fragment has not yet been added to the container, add it first
if (supportFragmentManager.findFragmentByTag(tag) == null) {
transaction.add(R.id.container, fragments[this.currentTabIndex], tag)
}
transaction.hide(fragments[previousTabIndex])
transaction.show(fragments[this.currentTabIndex])
transaction.commit()
}
Try this :
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment fragment = null;
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.container);
switch (item.getItemId()) {
case R.id.action_one:
// Switch to page one
if (!(currentFragment instanceof FragmentA)) {
fragment = FragmentA.newInstance();
}
break;
case R.id.action_two:
// Switch to page two
if (!(currentFragment instanceof FragmentB)) {
fragment = FragmentB.newInstance();
}
break;
case R.id.action_three:
// Switch to page three
if (!(currentFragment instanceof FragmentC)) {
fragment = FragmentC.newInstance();
}
break;
}
getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment, "TAG").commit();
return true;
}
});
That will get the current fragment in your container and if you click again on this fragment that will not re add the fragment.
setOnNavigationItemReselectedListener would be a better solution for that
Use setOnNavigationItemReselectedListener like this:
private BottomNavigationView.OnNavigationItemReselectedListener onNavigationItemReselectedListener
= new BottomNavigationView.OnNavigationItemReselectedListener() {
#Override
public void onNavigationItemReselected(#NonNull MenuItem item) {
Toast.makeText(MainActivity.this, "Reselected", Toast.LENGTH_SHORT).show();
}
};
and call it using:
navigation.setOnNavigationItemReselectedListener(onNavigationItemReselectedListener);
The best way i found to do it.
private void replace_fragment(Fragment fragment) {
String tag = fragment.getClass().getSimpleName();
FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
Fragment curFrag = getSupportFragmentManager().getPrimaryNavigationFragment();
Fragment cacheFrag = getSupportFragmentManager().findFragmentByTag(tag);
if (curFrag != null)
tr.hide(curFrag);
if (cacheFrag == null) {
tr.add(R.id.main_frame, fragment, tag);
} else {
tr.show(cacheFrag);
fragment = cacheFrag;
}
tr.setPrimaryNavigationFragment(fragment);
tr.commit();
}
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.nav_posts:
replace_fragment(new PostsFragment());
return true;
case R.id.nav_stores:
replace_fragment(new StoresFragment());
return true;
case R.id.nav_chats:
replace_fragment(new DiscussionsFragment());
return true;
case R.id.nav_account:
replace_fragment(new ProfileFragment());
return true;
}
return false;
}
};
I've improved #Viven's answers and written it with Kotlin. My version instantiates fragment only for the first time, hides/shows. I'm new at Kotlin so tell me if I can improve anything.
We need to hold a id to tag map:
private val fragmentTags = hashMapOf(
R.id.action_home to "home_fragment",
R.id.action_profile to "profile_fragment"
)
The listener code:
bottomNavigation.run {
setOnNavigationItemSelectedListener { menuItem ->
supportFragmentManager.beginTransaction()
.let { transaction ->
// hide current fragment
supportFragmentManager.primaryNavigationFragment?.let {
// if selected fragment's tag is same, do nothing.
if (it.tag == fragmentTags[menuItem.itemId]) {
return#setOnNavigationItemSelectedListener true
}
transaction.hide(it)
}
var fragment = supportFragmentManager.findFragmentByTag(fragmentTags[menuItem.itemId])
if (fragment == null) {
// instantiate fragment for the first time
fragment = when(menuItem.itemId) {
R.id.action_home -> HomeFragment()
R.id.action_profile -> ProfileFragment()
else -> null
}?.also {
// and add it
transaction.add(R.id.frame, it, fragmentTags[menuItem.itemId])
}
} else {
// if it's found show it
transaction.show(fragment)
}
transaction
.setPrimaryNavigationFragment(fragment)
.setReorderingAllowed(true)
}.commitNowAllowingStateLoss()
return#setOnNavigationItemSelectedListener true
}
//bottomNavigation things
itemIconTintList = null
selectedItemId = R.id.action_home
}
I solved this problem by adding a ViewPager to which I delegated all my navigation fragments. Its adapter (FragmentPagerAdapter) doesn't recreate the fragments instances when the user navigates through the BotoomNavigationView.
To achieve this, you have to complete 5 easy steps:
add a ViewPager to your layout;
implement its adapter:
class YourNavigationViewPagerAdapter(fm: FragmentManager,
private val param1: Int,
private val param2: Int)
: FragmentPagerAdapter(fm) {
override fun getItem(p0: Int) = when(p0) {
0 -> NavigationFragment1.newInstance(param1, param2)
1 -> NavigationFragment2.newInstance(param1, param2)
2 -> NavigationFragment3.newInstance(param1, param2)
else -> null
}
override fun getCount() = 3
}
don't forget to set the new adapter:
yourViewPager.adapter = YourNavigationViewPagerAdapter(supportFragmentManager, param1, param2)
set a OnNavigationItemSelectedListener to your BottomNavigationView like the following:
yourBottomNavigationView.setOnNavigationItemSelectedListener {
when(it.itemId) {
R.id.yourFirstFragmentMenuItem -> {
yourViewPager.currentItem = 0
true
}
R.id.yourSecondFragmentMenuItem -> {
yourViewPager.currentItem = 1
true
}
R.id.yourThirdFragmentMenuItem -> {
yourViewPager.currentItem = 2
true
}
else -> false
}
}
set a OnPageChangeListener to your ViewPager like the following:
yourViewPager.addOnPageChangeListener(object :
ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(p0: Int) {
}
override fun onPageScrolled(p0: Int, p1: Float, p2: Int) {
}
override fun onPageSelected(p0: Int) {
yourBottomNavigationView.menu.getItem(p0).isChecked = true
}
})
enjoy :)
I wrote an Extension function in Kotlin for the same.
fun FragmentManager.switch(containerId: Int, newFrag: Fragment, tag: String) {
var current = findFragmentByTag(tag)
beginTransaction()
.apply {
//Hide the current fragment
primaryNavigationFragment?.let { hide(it) }
//Check if current fragment exists in fragmentManager
if (current == null) {
current = newFrag
add(containerId, current!!, tag)
} else {
show(current!!)
}
}
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.setPrimaryNavigationFragment(current)
.setReorderingAllowed(true)
.commitNowAllowingStateLoss()
}
There are several test cases involved in proper navigation , I am pasting my code with all test cases checked.
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
switch (item.getItemId()) {
case R.id.dashboard:
Fragment fragment;
fragment = fragmentManager.findFragmentByTag(DashboardFragment.TAG);
if (fragment == null) {
fragment = new DashboardFragment();
fragmentTransaction.add(R.id.frame, fragment, DashboardFragment.TAG);
} else {
fragmentTransaction.detach(fragmentManager.getPrimaryNavigationFragment());
fragmentTransaction.attach(fragment);
}
fragmentTransaction.setPrimaryNavigationFragment(fragment);
fragmentTransaction.commitNow();
return true;
case R.id.expenses:
fragment = fragmentManager.findFragmentByTag(ExpenseFragment.TAG);
if (fragment == null) {
fragment = new ExpenseFragment();
fragmentTransaction.add(R.id.frame, fragment, ExpenseFragment.TAG);
} else {
fragmentTransaction.detach(fragmentManager.getPrimaryNavigationFragment());
fragmentTransaction.attach(fragment);
}
fragmentTransaction.setPrimaryNavigationFragment(fragment);
fragmentTransaction.commitNow();
return true;
case R.id.vehicle_parts:
Bundle bundle = new Bundle();
bundle.putInt("odometer", vehicle.getOdometer());
bundle.putString("vehicle_id", vehicle.get_id());
fragment = fragmentManager.findFragmentByTag(PartsFragment.TAG);
if (fragment == null) {
fragment = new PartsFragment();
fragment.setArguments(bundle);
fragmentTransaction.add(R.id.frame, fragment, PartsFragment.TAG);
} else {
fragmentTransaction.detach(fragmentManager.getPrimaryNavigationFragment());
fragmentTransaction.attach(fragment);
}
fragmentTransaction.setPrimaryNavigationFragment(fragment);
fragmentTransaction.commitNow();
return true;
case R.id.blog:
fragment = fragmentManager.findFragmentByTag(BlogFragment.TAG);
if (fragment == null) {
fragment = new BlogFragment();
fragmentTransaction.add(R.id.frame, fragment, BlogFragment.TAG);
} else {
fragmentTransaction.detach(fragmentManager.getPrimaryNavigationFragment());
fragmentTransaction.attach(fragment);
}
fragmentTransaction.setPrimaryNavigationFragment(fragment);
fragmentTransaction.commitNow();
return true;
}
return false;
Use Cicerone library for handling navigation easily.
https://github.com/terrakok/Cicerone
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.save: {
router.replaceScreen("your fragment1");
menuItem.setChecked(true);
break;
}
case R.id.search_lessons: {
router.replaceScreen("your fragment2");
menuItem.setChecked(true);
break;
}
case R.id.profile_student: {
router.replaceScreen("your fragment3");
menuItem.setChecked(true);
break;
}
}
return false;
}
How To stop recreating Fragment when it is Already Visible Using BottomNavigationView
Step 1--
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment fragment = null;
String valuestring;
**if (item.getItemId() == lastSelectedItemId) { // added this
Log.e( "both id is same","LULL" );
return true;
}**
switch (item.getItemId()) {
case R.id.navigation_category:
SetActioBarText( getString( R.string.label_actionbar_categories ) );
fragment = new CategoryFragment();
valuestring = "category";
break;
case R.id.navigation_favourite:
SetActioBarText( getString( R.string.label_actionbar_favourites ) );
fragment = new FavouriteFragment();
valuestring = "favourites";
break;
default:
throw new IllegalStateException( "Unexpected value: " + menuItem.getItemId() );
}
return loadFragment( fragment, valuestring ,menuItem);
}
Now Step 2---
private boolean loadFragment(Fragment fragment, String argument,MenuItem item) {
if (fragment != null) {
transaction = fragmentManager.beginTransaction();
transaction.addToBackStack( argument );
transaction.setTransition( FragmentTransaction.TRANSIT_FRAGMENT_FADE );
transaction.replace( R.id.frame_container, fragment, "demofragment").commitAllowingStateLoss();
lastSelectedItemId= item.getItemId();
return true;
}
return false;
}
no need to do this answers. actually what you really need is to save view in specific fragmet.
private View view;
if(view == null){
view = inflater.inflate(R.layout.call_fragment_edited, container, false);
}
so any time when you create new fragment you see current state

Fragment getting null in host activity

I want to call fragment method from another fragment, so I find relevant fragment in host activity and call method of this fragment. But some time I getting fragment null.
Suppose I want to save data of fragment A from all other fragment. There is 4 fragment like A, B, C and D. When I click save button from fragment A and B then it working fine but When I save data from fragment C and D then Fragment A getting null.
Here is my code :
HostActivity.cs
public class HostActivityView : MvxCachingFragmentCompatActivity<HostActivityViewModel>
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
try
{
SetContentView(Resource.Layout.HostActivityView);
var toolbar = FindViewById<Toolbar>(Resource.Id.myToolbar);
if (toolbar != null)
{
// Toolbar will now take on default actionbar characteristics
SetSupportActionBar(toolbar);
}
// View Pager
var viewPager = FindViewById<ViewPager>(Resource.Id.viewpager);
if (viewPager != null)
{
// Add tabs in view pager
var fragments = new List<MvxFragmentStatePagerAdapter2.FragmentInfo>
{
new MvxFragmentStatePagerAdapter2.FragmentInfo("Ticket", typeof(Tab_Ticket), typeof(TicketEditViewModel)),
new MvxFragmentStatePagerAdapter2.FragmentInfo("Employee", typeof(Tab_Employee), typeof(EmployeeViewModel)),
new MvxFragmentStatePagerAdapter2.FragmentInfo("Response", typeof(Tab_Correspondence), typeof(ResponseViewModel)),
new MvxFragmentStatePagerAdapter2.FragmentInfo("Expense", typeof(Tab_Expenses), typeof(ExpenseViewModel)),
};
viewPager.Adapter = new MvxFragmentStatePagerAdapter2(this, SupportFragmentManager, fragments);
}
var tabLayout = FindViewById<TabLayout>(Resource.Id.tabs);
tabLayout.SetupWithViewPager(viewPager);
}
catch (Exception ex)
{
Mvx.Resolve<IUserInteraction>().Alert(ex.Message);
}
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
switch (item.ItemId)
{
case Resource.Id.menu_accept:
Tab_Ticket tabTicket = (Tab_Ticket)SupportFragmentManager.FindFragmentByTag("Tab_Ticket");
if (tabTicket != null)
{
tabTicket.OnOptionsItemSelected(item);
handled = true;
}
break;
}
}
}
Here is my fragment Code :
Fragment.cs :
public class Tab_Ticket : MvxFragment<TicketEditViewModel>
{
public override View OnCreateView(Android.Views.LayoutInflater inflater, Android.Views.ViewGroup container, Android.OS.Bundle savedInstanceState)
{
var ignored = base.OnCreateView(inflater, container, savedInstanceState);
var view = this.BindingInflate(Resource.Layout.frg_tab_ticket, null);
return view;
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
switch (item.ItemId)
{
case Resource.Id.menu_accept:
this.ViewModel.CmdTicketSave.Execute(null);
return true;
break;
}
}
}
Please suggest me where I going wrong?
Thank you
Finally got the solution for this problem.
In host activity increase the view pager page limit. Because I have 5 fragments and default it display 3 fragments in support fragment manager. So sometime getting first fragment null. Added this line in my code and it working fine.
viewPager.OffscreenPageLimit = 6;
Here is more details about this issue.

Categories

Resources