When I use backStackEntryCount > 1. popbackstack() it is not navigating to any of the fragments of my bottom navigation
What I need to know is, how to stack fragments one time only, so I can press the back button once which will be navigating to the Activity.
private fun navBottomClick() {
bottomNavigationView?.setOnItemSelectedListener {
when (it.itemId) {
R.id.workout -> {
var fragmentTransaction: FragmentTransaction =
supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.container, WorkoutFragment(), "WORKOUT")
fragmentTransaction.addToBackStack("WORKOUT")
fragmentTransaction.commit()
}
R.id.health -> {
var fragmentTransaction: FragmentTransaction =
supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.container, HealthyFoodFragment(), "HealthFood")
fragmentTransaction.addToBackStack("HealthFood")
fragmentTransaction.commit()
}
}
true
}
}
without backStack you can try this
override fun onBackPressed() {
if (binding.bottomNavigation.selectedItemId == R.id.home) {
moveTaskToBack(true)
} else {
binding.bottomNavigation.selectedItemId = R.id.home
val homeFragment = HomeFragment.newInstance()
openFragment(homeFragment)
}
}
Try to print log supportFragmentManager.backStackEntryCount.
And my opinion is the condition is supportFragmentManager.backStackEntryCount > 0.
Related
I'm using Bottom navigation Bar and Kakao Login API. When I choose account menu in bottom bar if I need login then replace fragment to fragmentAccount else I don't need login then replace fragmnet to fragment2Account. I tried to do this but it makes Error. I want to know why it makes error and how can fix this.
val transaction: FragmentTransaction = supportFragmentManager.beginTransaction()
val fragmentAccount = AccountFragment()
val fragment2Account = Account2Fragment()
when(p0.itemId){
R.id.account -> {
if (AuthApiClient.instance.hasToken()) {
UserApiClient.instance.accessTokenInfo { _, error ->
if (error != null) {
transaction.replace(R.id.frame, fragmentAccount, "Account")
}
else transaction.replace(R.id.frame, fragment2Account, "Account")
}
}
else transaction.replace(R.id.frame, fragmentAccount, "Account")
}
}
transaction.addToBackStack(null)
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
transaction.commit()
return true
Error code ↓↓↓
kotlin.UninitializedPropertyAccessException: lateinit property hosts has not been initialized
I'm trying to popBackStack on childFragmentManager if a childFragment is added but when I check if getChildFragmentManager.getBackStackEntryCount() is > 0, it's always false.
Any suggestions on this isue?
#Override
public void onBackPressed() {
if (getSupportFragmentManager().getBackStackEntryCount() < 2) {
this.finishAffinity();
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);
} else {
FragmentManager fm = getSupportFragmentManager();
for (Fragment frag : fm.getFragments()) {
FragmentManager childFm = frag.getChildFragmentManager();
Log.i("RAZZZVI", "Number " + childFm.getBackStackEntryCount());
if (childFm.getBackStackEntryCount() > 0) {
childFm.popBackStackImmediate();
return;
} else {
getSupportFragmentManager().popBackStackImmediate();
}
}
}
}
And this is how I add the fragments
layout_chart1.setOnClickListener {
child_fragment_container.visibility = View.VISIBLE
val transaction = childFragmentManager.beginTransaction()
transaction.addToBackStack(null)
.setCustomAnimations(R.anim.pull_in_right, R.anim.push_out_right)
.add(R.id.child_fragment_container, PieChartDetails(chartsViewModel), "STATUS_DETAILS")
.commit()
}
layout_chart2.setOnClickListener {
child_fragment_container.visibility = View.VISIBLE
val transaction = childFragmentManager.beginTransaction()
transaction.addToBackStack(null)
.setCustomAnimations(R.anim.pull_in_right, R.anim.push_out_right)
.add(R.id.child_fragment_container, BarChartDetails(chartsViewModel))
.commit()
}
getBackStackEntryCount() is 0 because you have not added any Fragment transactions to the FragmentManager's back stack.
Note that each FragmentManager has its own backstack (so a Fragment's child FragmentManager stack would be different from the containing Activity's fragment backstack), and it is also distinct from the application's Activity back stack.
If you were to add your transaction to the back stack like so, it would return 1:
fragmentManager.beginTransaction()
.replace(R.id.frame_layout, stepsFragment)
.addToBackStack(null)
.commit();
I have a problem switching between fragments using my bottom navigation view.it loads first and i can switch between the fragments for the first time but if a fragment was already loaded and try to navigate back to it. My app crashes showing an error i listed below.
Here is my code
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener{
when (it.itemId) {
R.id.vegetables -> {
showFragment(VegetablesFragment(),VegetablesFragment().javaClass.simpleName)return#OnNavigationItemSelectedListener true
}
R.id.fruits -> {
showFragment(FruitsFragment(),FruitsFragment().javaClass.simpleName)
return#OnNavigationItemSelectedListener true
}
R.id.grocery -> {
showFragment(GroceryFragment(),GroceryFragment().javaClass.simpleName)
return#OnNavigationItemSelectedListener true
}
else -> return#OnNavigationItemSelectedListener false
}
}
private fun showFragment(fragment : Fragment ,fragmentName :String) {
val transaction = manager.beginTransaction()
val currentFragment = manager.primaryNavigationFragment
if (currentFragment != null) {
transaction.hide(currentFragment)
}
val fragmentTemp = manager.findFragmentByTag(fragmentName)
if (fragmentTemp == null){
transaction.add(R.id.fragmentHolder,fragment,fragmentName)
Log.d("ac1001","case 2")
} else {
Log.d("ac1001","case 1")
transaction.show(fragment)
}
transaction.setPrimaryNavigationFragment(fragment)
transaction.setReorderingAllowed(true)
transaction.commitNowAllowingStateLoss()
}
Error:
java.lang.IllegalArgumentException: Fragment VegetablesFragment{d4a075d (f9739b50-c0fc-47c1-80d1-dc413ffd5a09)} is not an active fragment of FragmentManager FragmentManager{5b1a8d2 in HostCallbacks{2810da3}}
I tried searching everywhere but couldn't find an answer.
The error is occuring on my transaction.show(..)
Thanks in advance.
Try with this code. change your showFragment function to ->
fun showFragment(fragment: Fragment ,fragmentName :String) {
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragmentHolder, fragment)
transaction.addToBackStack(fragmentName)
transaction.commit()
}
Hope this will help you.
Fixed it by changing my code to:
private fun showFragment(fragment : Fragment ,fragmentName :String) {
val transaction = manager.beginTransaction()
val currentFragment = manager.primaryNavigationFragment
if (currentFragment != null) {
transaction.hide(currentFragment)
}
val fragmentTemp = manager.findFragmentByTag(fragmentName)
if (fragmentTemp == null){
transaction.add(R.id.fragmentHolder,fragment,fragmentName)
} else {
transaction.show(fragmentTemp)
}
transaction.setPrimaryNavigationFragment(fragmentTemp)
transaction.setReorderingAllowed(true)
transaction.commitNowAllowingStateLoss()
}
I have set of Fragments navigates inside activity. While I called findFragmentByTag() the fragments onCreateView() and onViewCreated() are called again and the data is reset to normal. how to prevent the recreation of fragment?
You can have look on the code of the advanced navigation in android samples
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 ->
// 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()
.attach(selectedFragment)
.setPrimaryNavigationFragment(selectedFragment)
.apply {
// Detach all other Fragments
graphIdToTagMap.forEach { _, fragmentTagIter ->
if (fragmentTagIter != newlySelectedItemTag) {
detach(fragmentManager.findFragmentByTag(firstFragmentTag)!!)
}
}
}
.addToBackStack(firstFragmentTag)
.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)
.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
}
this example code you can find the full code here
https://github.com/android/architecture-components-samples/tree/master/NavigationAdvancedSample
I have a simple activity with a BottomNavigationView. I'm using fragments to implement the contents of the activity for the different pages.
When the user presses the back button, it's supposed to go back to the previously looked at page. The problem is, when you repeatedly switch back and forth between the pages (fragments), this entire history is recorded. Take this example:
A -> B -> A -> B -> C -> A -> C
Pressing the back button would result in the reverse, but instead I want this behaviour (I noticed it in the Instagram app):
C -> A -> B -> Exit App
So every fragment should only have one entry in the backstack. How do I do this? I do I remove the previous transactions for a fragment from the stack?
Is this at all possible using a FragmentManager? Or do I have to implement my own?
My Activity with the BottomNavigationView:
class ActivityOverview : AppCompatActivity() {
// Listener for BottomNavigationView
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_home -> {
// "Home" menu item pressed
setActiveFragment(resources.getString(R.string.tag_fragment_home))
return#OnNavigationItemSelectedListener true
}
R.id.navigation_dashboard -> {
// "Dashboard" menu item pressed
return#OnNavigationItemSelectedListener true
}
R.id.navigation_settings -> {
// "Settings" menu item pressed
setActiveFragment(resources.getString(R.string.tag_fragment_settings))
return#OnNavigationItemSelectedListener true
}
}
false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_overview)
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
navigation.menu.findItem(R.id.navigation_home).setChecked(true)
// Set initial fragment
setActiveFragment(resources.getString(R.string.tag_fragment_home))
}
override fun onBackPressed() {
// > 1 so initial fragment addition isn't removed from stack
if (fragmentManager.backStackEntryCount > 1) {
fragmentManager.popBackStack()
} else {
finish()
}
}
// Update displayed fragment
fun setActiveFragment(tag: String) {
val fragment = if (fragmentManager.findFragmentByTag(tag) != null) {
// Fragment is already initialized
if (fragmentManager.findFragmentByTag(tag).isVisible) {
// Fragment is visible already, don't add another transaction
null
} else {
// Fragment is not visible, add transaction
fragmentManager.findFragmentByTag(tag)
}
} else {
// Fragment is not initialized yet
when (tag) {
resources.getString(R.string.tag_fragment_home) -> FragmentHome()
resources.getString(R.string.tag_fragment_settings) -> FragmentSettings()
else -> null
}
}
if (fragment != null) {
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.container_fragment, fragment, tag)
transaction.addToBackStack(null)
transaction.commit()
}
}
}
At this point I'm pretty sure it doesn't work with FragmentManager, so I created a class to implement a stack that doesn't allow duplicates:
class NoDuplicateStack<T> {
val stack: MutableList<T> = mutableListOf()
val size: Int
get() = stack.size
// Push element onto the stack
fun push(p: T) {
val index = stack.indexOf(p)
if (index != -1) {
stack.removeAt(index)
}
stack.add(p)
}
// Pop upper element of stack
fun pop(): T? {
if (size > 0) {
return stack.removeAt(stack.size - 1)
} else {
return null
}
}
// Look at upper element of stack, don't pop it
fun peek(): T? {
if (size > 0) {
return stack[stack.size - 1]
} else {
return null
}
}
}
I then integrated this class into my activity:
class ActivityOverview : AppCompatActivity() {
val fragmentsStack = NoDuplicateStack<String>()
val fragmentHome = FragmentHome()
val fragmentSettings = FragmentSettings()
val fragmentHistory = FragmentHistory()
// Listener for BottomNavigationView
private val mOnNavigationItemSelectedListener = ...
override fun onCreate(savedInstanceState: Bundle?) {
...
}
override fun onBackPressed() {
if (fragmentsStack.size > 1) {
// Remove current fragment from stack
fragmentsStack.pop()
// Get previous fragment from stack and set it again
val newTag = fragmentsStack.pop()
if (newTag != null) {
setActiveFragment(newTag)
}
} else {
finish()
}
}
// Update displayed fragment
fun setActiveFragment(tag: String) {
val fragment = when (tag) {
resources.getString(R.string.tag_fragment_home) -> fragmentHome
resources.getString(R.string.tag_fragment_settings) -> fragmentSettings
resources.getString(R.string.tag_fragment_history) -> fragmentHistory
else -> null
}
if (fragment != null && !fragment.isVisible) {
fragmentManager.beginTransaction()
.replace(R.id.container_fragment, fragment, tag)
.commit()
fragmentsStack.push(tag)
}
}
}
I also faced the same problem, I did this which uses the system stack
val totalFragments = supportFragmentManager.backStackEntryCount
if (totalFragments != 0) {
val removed = supportFragmentManager.getBackStackEntryAt(totalFragments - 1)
poppedFragments.add(removed.name!!)
for (idx in totalFragments - 1 downTo 0) {
val fragment = supportFragmentManager.getBackStackEntryAt(idx)
if (!poppedFragments.contains(fragment.name)) {
supportFragmentManager.popBackStack(fragment.name, 0)
return
}
}
finish()
return
}
super.onBackPressed()
and then added this while launching the fragment
if (poppedFragments.contains(tag)) {
poppedFragments.remove(tag)
}