I use a BottomNavigationView with four pages. If I want to add 4 fragments to that it is Ok but when I want to replace new fragment with old fragment in one of the BottomNavigationView page and restore it when item click, the first fragment open again. How can I restore the last fragment? Should I use different FrameLayout in the first BottomNavigationView?
Try this:
bottomNavView.setOnNavigationItemSelectedListener(object : BottomNavigationView.OnNavigationItemSelectedListener {
override fun onNavigationItemSelected(item: MenuItem): Boolean {
clearAllBackStack()
when (item.itemId) {
R.id.itemHome -> {
// load fragment here
}
R.id.itemMyAccount -> {
// load fragment here
}
R.id.itemSettings -> {
// load fragment here
}
R.id.itemNotfications -> {
// load fragment here
}
}
return true
}
})
add this method:
fun clearAllBackStack() {
for (i in 0 until supportFragmentManager.backStackEntryCount) {
supportFragmentManager.popBackStack()
}
}
Note: also add fragment to backstack whenever you load fragment, add this when you load fragment: fragmentTransaction.addToBackStack(null)
Related
I attached bottom navigation to my app and call changeFragment. When I call changeFragment, for example, A fragment to B fragment, I expected to be called onStop at A fragment. However, Any lifecycle callback isn't called.. Why is happened?
THIS IS MY CODE..
private fun setBottomNavigation() {
binding.bottomNavigation.run {
setOnItemSelectedListener { menu ->
when (menu.itemId) {
R.id.navigation_menu_main ->
changeFragment(mainFragment, MainFragment.TAG, menu.itemId)
R.id.navigation_menu_money ->
changeFragment(moneyFragment, MoneyFragment.TAG, menu.itemId)
R.id.navigation_menu_life ->
changeFragment(lifeFragment, LifeFragment.TAG, menu.itemId)
R.id.navigation_menu_food ->
changeFragment(foodFragment, FoodFragment.TAG, menu.itemId)
R.id.navigation_menu_menu ->
changeFragment(menuFragment, MenuFragment.TAG, menu.itemId)
else -> false
}
}
selectedItemId = R.id.navigation_menu_main
}
}
private fun changeFragment(fragment: Fragment, tag: String, menuId: Int): Boolean {
supportFragmentManager.commit {
hide(currentFragment)
showFragment(fragment, tag)
}
currentFragment = fragment
return true
}
private fun FragmentTransaction.showFragment(fragment: Fragment, tag: String) {
supportFragmentManager.executePendingTransactions()
if (fragment.isAdded) {
show(fragment)
} else {
add(binding.container.id, fragment, tag).show(fragment)
}
}
You can use add() and replace() to change fragments in your activity. And the difference between add and replace is that:
add simply add another fragment to the fragment container and does not destroy the existing fragments so they remain active and lifecycle events for those existing fragments are not called.
replace removes the existing fragments to add a new fragment which means lifecycle events like onPause, onStop, and onCreateView will be invoked.
I have a small app with on activity and two fragments inside. The fragments are loaded with the BottomNavitationView.
MonitoringFragment gets loaded on the OnCreate in the activity.
I want to add this one to the backstack so when I'm inside the second fragment (ConnectionFragment) and i press back I get to the first fragment. This works fine. However the BotttonNavigationView doesn't get updated (doesn't set the first item as selected when returning from second fragment. picture 3). I assume it doesn't handle this behavior by itself and I have to implement it myself but every attempt I made was unsuccessfull.
Activity code:
Fragment activeFragment = null;
BottomNavigationView bottomNavigationView = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bottomNavigationView = findViewById(R.id.bottom_navigation);
bottomNavigationView.setOnNavigationItemSelectedListener(item -> {
switch (item.getItemId()) {
case R.id.monitoring:
setCurrentFragment(new MonitoringFragment(), false);
break;
case R.id.connection:
setCurrentFragment(new ConnectionFragment(), true);
break;
}
return true;
});
setCurrentFragment(new MonitoringFragment(), true);
}
private void setCurrentFragment(Fragment fragment, boolean addToBackStack) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragment);
if (addToBackStack) {
fragmentTransaction.addToBackStack(null);
}
fragmentTransaction.commit();
activeFragment = fragment;
}
Thanks!
Yes, BottomNavigationView is not setup with fragmentManager. You might set prop select tab by yourself, when a fragment gets resumed.
Or you can use navigation component with BottomNavigationView, those working ok together.
With the second approach when pressing back button will not return you from 2nd fragment to 1st one.
Faced a similar problem, here is how I solved it
I added OnDestinationChangedListener to my navcontroller, and created several arraylists with destination label of fragments in my progect.
When current destination is in one of these fragments, bottom menu button is checked
I hope the code example is clearer than my explanation)
val destListNews = arrayListOf(
"news_fragment",
"NewsViewingFragment"
)
val destListAds = arrayListOf(
"ads_view_fragment",
"ads_fragment",
"AdsAddFragment"
)
val destListPass = arrayListOf(
"pass_fragment",
"pass_creation_fragment",
"PassViewFragment"
)
val destListVotes = arrayListOf(
"VotesListFragment",
"VotesAddFragment",
"ChooseVoteTypeFragment"
)
navController.addOnDestinationChangedListener { controller, destination, arguments ->
when (destination.label) {
in destListNews -> {
bottom_nav.menu.getItem(0).isChecked = true
}
in destListAds -> {
bottom_nav.menu.getItem(1).isChecked = true
}
in destListPass -> {
bottom_nav.menu.getItem(2).isChecked = true
}
in destListVotes -> {
bottom_nav.menu.getItem(3).isChecked = true
}
else -> {
bottom_nav.menu.getItem(4).isChecked = true
}
}
}
In my app I have main activity with container and bottomNavigationView with 5 items. At container I can show fragments with information. I can change fragment by clicking on buttons which are placed on bottomnavigation view. I noticed the problem:
when I click on items very quickly I receive or app crashes because my view is looking for view-elements or another problem is that my parent activity toolbar show wrong title/subtitle. For example I can click on item 1 and then quickly tap on item 2 and if I continue tapping toooo quickly I can see that toolbar show content which demand to wrong fragment. I thought that maybe checking fragment visibility will solve this problem but it didn't help me. I tried also to check if fragment is visible from onCreateView at fragment and it didn't help me too. Here is the code of bottomNavigationView listener:
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
val transaction = supportFragmentManager.beginTransaction()
val bundle = Bundle()
when (item.itemId) {
R.id.item_id_1-> {
supportActionBar!!.show()
[....]
transaction.replace(R.id.contentContainerT, jobList).addToBackStack(null).commit()
textSetter("Item_0", resources.getString(R.string.all_jobs) + " " + sp!!.getString("all_jobs", ""))
return#OnNavigationItemSelectedListener true
}
R.id.item_id_2-> {
bottom_navigation_t.menu.findItem(R.id.received_mess).title.chunked(1)
disableShowHideAnimation(supportActionBar!!)
supportActionBar!!.show()
[.....]
textSetter(resources.getString(R.string.title_activity_message_center), resources.getString(R.string.received))
transaction.replace(R.id.contentContainerT, messageList).addToBackStack(null).commit()
return#OnNavigationItemSelectedListener true
}
R.id.item_id_3-> {
disableShowHideAnimation(supportActionBar!!)
supportActionBar!!.hide()
transaction.replace(R.id.contentContainerT, PersonalPage()).addToBackStack(null).commit()
return#OnNavigationItemSelectedListener true
}
R.id.item_id_4-> {
disableShowHideAnimation(supportActionBar!!)
supportActionBar!!.show()
[....]
transaction.replace(R.id.contentContainerT, notepadScr).addToBackStack(null).commit()
return#OnNavigationItemSelectedListener true
}
R.id.item_id_5-> {
disableShowHideAnimation(supportActionBar!!)
supportActionBar!!.hide()
textSetter(resources.getString(R.string.more_bottom_nav), "")
transaction.replace(R.id.contentContainerT, MoreScreenK()).addToBackStack(null).commit()
return#OnNavigationItemSelectedListener true
}
}
false
}
maybe problem at the setter function, I don't know.
The problem
I'm using the BottomNavigationView from the Android Design Support Library on one of my Activities, alongside with Fragments for each navigation item.
Each time I select an item on the bar, I do a fragment transaction, like the snippet below (some parts of the code was removed for brevity):
private var fragmentToSet: Fragment? = null
private val onNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
fragmentToSet = when (item.itemId) {
// Choose fragment based on selection
// ...
}
// ...
supportFragmentManager.beginTransaction()
.replace(R.id.container, fragmentToSet)
.commit()
}
The problem is... The bottom bar animation gets super laggy, and only finishes after the fragment is fully loaded and displayed on the screen.
This issue is not exactly new since it can also happen while using the Navigation Menu, but at least it's possible to solve it by using the DrawerLayout.DrawerListener and do the actual Fragment transaction only after the drawer is closed.
What I've tried so far
I tried to "cache" the fragments, holding their reference to avoid recreating the objects every time (e.g. MyFragment.newInstance()), but that didn't work.
I also tried to use handlers, which kinda solved the problem, but it might lead me to an exception in some cases. Something like the snippet below:
handler.postDelayed({changeFragment(fragmentToSet!!)}, 200)
Is there a way to solve this issue without using handlers (or other async calls), on a similar fashion to this solution while using the Navigation Menu?
I handled this situation by hiding and showing fragments using fragment manager. I wrote a sample code to deal with it as below.
class MainActivity : BaseActivity() {
private val homeFragment = HomeFragment.newInstance()
private val categoryFragment = CategoryFragment.newInstance()
private val searchFragment = SearchFragment.newInstance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
navigation.menu.findItem(R.id.navigation_home).isChecked = true
supportFragmentManager.beginTransaction()
.add(R.id.containerFrameLayout, homeFragment)
.add(R.id.containerFrameLayout, categoryFragment)
.add(R.id.containerFrameLayout, searchFragment)
.commit()
setTabStateFragment(TabState.HOME).commit()
}
override fun onBackPressed() {
if (supportFragmentManager.backStackEntryCount > 0 || !homeFragment.isHidden) {
super.onBackPressed()
} else {
setTabStateFragment(TabState.HOME).commit()
navigation.menu.findItem(R.id.navigation_home).isChecked = true
}
}
private fun setTabStateFragment(state: TabState): FragmentTransaction {
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
val transaction = supportFragmentManager.beginTransaction()
transaction.setCustomAnimations(R.anim.fragment_enter, R.anim.fragment_exit)
when (state) {
TabState.HOME -> {
transaction.show(homeFragment)
transaction.hide(categoryFragment)
transaction.hide(searchFragment)
}
TabState.CATEGORY -> {
transaction.hide(homeFragment)
transaction.show(categoryFragment)
transaction.hide(searchFragment)
}
TabState.SEARCH -> {
transaction.hide(homeFragment)
transaction.hide(categoryFragment)
transaction.show(searchFragment)
}
}
return transaction
}
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_home -> {
setTabStateFragment(TabState.HOME).commit()
return#OnNavigationItemSelectedListener true
}
R.id.navigation_category -> {
setTabStateFragment(TabState.CATEGORY).commit()
return#OnNavigationItemSelectedListener true
}
R.id.navigation_search -> {
setTabStateFragment(TabState.SEARCH).commit()
return#OnNavigationItemSelectedListener true
}
}
false
}
internal enum class TabState {
HOME,
CATEGORY,
SEARCH,
}
}
I am using a BottomNavigationView for a Tab-Interface in my main activity. in onCreate(), the switchTab method is called with the initial fragment. Tapping the the bottom navigation calls switchTab() for the respective tabs and should hide the current and display the new one. If the fragment was not added to the SupportFragmentManager, it gets added, otherwise it will be shown. Here's my code snippet:
private fun switchTab(fragment: Fragment, tag: String): Boolean {
val currentFragment = supportFragmentManager.fragments.find { it.tag == tag }
val ta = supportFragmentManager.beginTransaction()
if (currentFragment != null) {
ta.hide(currentFragment)
}
if (supportFragmentManager.fragments.contains(fragment)) {
ta.show(fragment)
} else {
ta.add(R.id.contentContainer, fragment, tag)
}
ta.commit()
return true
}
Now the problem is that sometimes two fragments are visible and overlay each other, making the userinterface unusable. How can this happen?