I am working on android application that contains five fragment on an activity, What I want is as the fragment 1 is opened and I back-press it comes to Main fragment and same as I press back-press from fragment 5 it also comes to Main fragment.
and When I press on Backpress from MainFragment, the App should Exit.
I have Gone through this link Link
and I have also added the Dispatcher but It not met my requirement.
Like I am always opening each fragment like this
private fun ShowQRCodeFragment() {
val newFragment: Fragment = QrCodeScanningFragment()
val transaction1: FragmentTransaction = supportFragmentManager.beginTransaction()
transaction1.replace(R.id.frameLayout, newFragment)
transaction1.addToBackStack(null)
transaction1.commit()
}
Updated the transaction
private fun FunctionNewSettings() {
val newFragment: Fragment = CustomSettingsFragment()
val transaction1: FragmentTransaction = supportFragmentManager.beginTransaction()
transaction1.replace(R.id.frameLayout, newFragment)
transaction1.addToBackStack("namedata")
fragmentManager.popBackStack()
transaction1.commit()
}
You should use addToBackStack() while fragment transaction. This will allow you to go to the previous fragment on back-press.
For the app exit case, check if the current fragment is MainFragment with the help of fragment tag and calling fragmentmanager.popBackStack() or super.onBackPressed() accordingly.
In MainFragment, use
override fun onAttach(context: Context) {
super.onAttach(context)
val callback = object : OnBackPressedCallback(
true // default to enabled
) {
override fun handleOnBackPressed() {
requireActivity().finish()
}
}
requireActivity().onBackPressedDispatcher.addCallback(
this, // LifecycleOwner
callback
)
}
In another fragments, use
override fun onAttach(context: Context) {
super.onAttach(context)
val callback = object : OnBackPressedCallback(
true // default to enabled
) {
override fun handleOnBackPressed() {
for (i in 0 until (requireActivity() as FragmentActivity).supportFragmentManager.backStackEntryCount) {
activity.supportFragmentManager.popBackStack()
}
}
}
requireActivity().onBackPressedDispatcher.addCallback(
this, // LifecycleOwner
callback
)
}
if u want to go back to the previous fragment first use
addToBackStack()
and if you want to exit the app/activity by using onBackPressed from activity then in MainFragment use
getActivity().onBackPressed();
if you want to finish the activity from Fragment use
getActivity().finish();
You can also replace existing fragment when user clicks Back button using
fragmentTransaction.replace(int containerViewId, Fragment fragment, String tag)
Solution
Override onBackPressed() method inside your activity.
override fun onBackPressed() {
val count = supportFragmentManager.backStackEntryCount
if (count > 1) {
repeat(count - 1) { supportFragmentManager.popBackStack() }
} else {
finish()
}
}
You don't need to mess around with the back button behaviour if you're just switching fragments around, and you shouldn't need to pop the backstack either.
The backstack is just a history, like the back button on your browser. You start with some initial state, like an empty container layout. There's no history before this (nothing on the backstack), so if you hit back now, it will back out of the Activity completely.
If you start a fragment transaction where you add a new fragment to that container, you can use addToBackStack to create a new "step" in the history. So it becomes
empty container -> added fragment
and if you hit back it takes a step back (pops the most recent state off the stack)
empty container
if you don't use addToBackStack, the change replaces the current state on the top of the stack
(with addToBackStack)
empty container -> fragmentA -> fragmentB
(without it)
empty container -> fragmentB
so usually you'll skip adding to the backstack when you add your first fragment, since you don't want an earlier step with the empty container - you want to replace that state
empty container
(add mainFragment without adding the transaction to the backstack)
mainFragment
and now when you're at that first state showing mainFragment, the back button will back out of the activity
So addToBackStack makes changes that are added to the history, and you can step back through each change. Skipping it basically alters the last change instead of making a new one. You can think of it like adding to the backstack is going down a level, so when you hit back you go back up to the previous level. Skipping the add keeps you on the same level, and just changes what you're looking at - hitting back still takes you up a level.
So you can use this to organise the "path" the back button takes, by adding new steps to the stack or changing the current one. If you can write out the stack you want, where the back button takes you back a step each time, you can create it!
One last thing - addToBackStack takes a String? argument, which is usually null, but you can pass in a label for the step you're adding. This allows you to pop the backstack all the way back to a certain point in the history, which is like when a browser lets you jump to the previous site in the history, and not just the last page.
So you can add a name for the transaction, like "show subfragment" when you're adding your first subfragment on top of mainFragment, meaning you can use popBackstack with that label to jump straight to the initial mainFragment state, where the next back press exits the activity. This is way more convenient than popping each step off the backstack, and keeping track of how many you need to do - you can just jump back in the history to a defined point
Related
In my app, I have a TopActionBar fragment that is loaded on the MainActivity that loads a MaterialToolbar, along with my navigation drawer. I have a FrameLayout in this fragment that I replace with fragments to navigate between pages. When I replace a fragment (using a function I have defined in a utils.kt file), I am tracking the fragments that are loaded for the first time and adding them to the BackStack so that I can pop them and prevent duplicates of that fragment from being added to the BackStack. Here is the relevant logic for how that is being managed in my Utils.kt file:
fun replaceFragment(destinationFragment : Fragment,
currentFragment: String,
title : String,
initialLaunch: Boolean = false
) {
val destinationFragmentName = destinationFragment.javaClass.simpleName
val fragmentTag : Fragment? = fragmentManager.findFragmentByTag(destinationFragmentName)
if(destinationFragmentName !== currentFragment || initialLaunch) {
val fragmentTransaction = fragmentManager.beginTransaction()
// some logic to determine animations depending on the fragment being replaced
if (fragmentTag == null) {
fragmentTransaction.replace(R.id.frame_layout, destinationFragment, destinationFragmentName)
fragmentTransaction.addToBackStack(destinationFragmentName)
} else { // re-use the old fragment
fragmentTransaction.replace(R.id.frame_layout, destinationFragment, destinationFragmentName)
}
fragmentTransaction.commit()
}
}
And then this is how I have overwritten the onBackPressed function in my MainActivity:
override fun onBackPressed() {
if (fragmentManager.backStackEntryCount > 0) {
fragmentManager.popBackStackImmediate()
} else {
super.onBackPressed()
}
}
A couple of things aren't working properly. Take this example flow of fragments below:
A -> B -> C -> B -> C
When I press back I get this flow:
BC -> AC -> App Close
Here multiple Fragments are being displayed at the same time.
What I expect to happen is:
C -> B -> A -> App Close
Can someone maybe offer some insights into why this is occurring and what I can do to fix this? If I don't conditionally addToBackStack, and just addToBackStack for every single Fragment I replace, it works fine, but I don't want the multiple copies in the BackStack. I need to keep the most recent instance of each Fragment in the BackStack. So in my example:
A -> B -> C -> B -> C
The BackStack would no longer have the first C, just the most recent one.
As per the FragmentManager guide:
When you call addToBackStack() on a transaction, note that the transaction can include any number of operations, such as adding multiple fragments, replacing fragments in multiple containers, and so on. When the back stack is popped, all of these operations are reversed as a single atomic action. If you've committed additional transactions prior to the popBackStack() call, and if you did not use addToBackStack() for the transaction, these operations are not reversed. Therefore, within a single FragmentManager, avoid interleaving transactions that affect the back stack with those that do not.
So what you are experiencing is the operation you did with addToBackStack being reversed (causing your original copy of B to reappear) while not touching the new C you did not use addToBackStack.
The FragmentManager's back stack is just that: a stack. That means you can't remove B from the stack unless it is at the top of the back stack. That means there's no way to remove B from the middle of the stack without using something like the support for multiple back stacks to completely swap between independent back stacks.
If you just want to make sure there is only one copy of B on the top of the stack, you'll want to popBackStack() to remove the topmost if the names are the same before unconditionally using addToBackStack() on your new instance.
I have two fragments; HomeFragment & NextFragment and I go to the next one with a button from the base fragment. Then via back press I want to go back to the first one, however I end up at the apps home screen. A solution
requireActivity().onBackPressedDispatcher
.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
findNavController().popBackStack()
}
})
was given here but it did not work. I am using ViewPager2 and saw that the home fragment is destroyed when I go to the next one. How can I solve the problem?
I have three fragments A, B and C. And I'm using navHostFragment container in MainActivity. So the application goes from A -> B using kotlin extension function findNavController().navigate... and then go from B to C using same function. All works fine till here.
Now in Fragment C, I'm replacing different elements on fragment C using
activity?.supportFragmentManager
?.beginTransaction()
?.replace(R.id.list_container, someFragment)
?.addToBackStack("some_frag_id")
?.commit()
The list_container is replaced with someFragment. After this when I press physical back button Fragment C pops out and my app goes to Fragment B while what I expect it to restore replaced list_container i.e. whatever was there before replacement.
I'm also overiding this in my MainActivity
override fun onBackPressed() {
val count = supportFragmentManager.backStackEntryCount
if (count == 0) {
super.onBackPressed()
//additional code
}
else {
supportFragmentManager.popBackStack()
}
}
I'm not sure what is missing here. I have read a lot of solutions on stackoverflow but none worked to my satisfaction. Please guide.
If you are adding a Fragment to a View within a Fragment, you must always use the childFragmentManager - using activity?.supportFragmentManager is always the wrong FragmentManager to use in that case.
Besides fixing cases with restoring state (which would not work when using the wrong FragmentManager), this also ensures that the default behavior for dispatching onBackPressed() down the FragmentManager hierarchy will work out the box - you should not need any logic at all in onBackPressed() to have the pop work correctly.
If you need to intercept the back button in Fragment C, you should follow the providing custom back documentation to register an OnBackPressedDispatcher - you should not override onBackPressed() even in those cases.
I have 2 fragment call CreateRoomFragment and DisplayPhotoFragment,the navigation graph is look like this:
<navigation>
<fragment
android:id="#+id/createRoomFragment"
android:name="package.room.CreateRoomFragment"
android:label="Create a room"
tools:layout="#layout/fragment_create_room">
<action
android:id="#+id/action_createRoomFragment_to_roomFragment"
app:destination="#id/roomFragment" />
<action
android:id="#+id/action_createRoomFragment_to_displayPhotoFragment"
app:destination="#id/displayPhotoFragment" />
</fragment>
<fragment
android:id="#+id/displayPhotoFragment"
android:name="package.fragment.DisplayPhotoFragment"
android:label="fragment_display_photo"
tools:layout="#layout/fragment_display_photo" >
<argument android:name="bitmap"
app:argType="android.graphics.Bitmap"/>
</fragment>
So when I wanna to move from CreateRoomFragment to DisplayPhotoFragment,I use the do as below:
NavDirections action = CreateRoomFragmentDirections.actionCreateRoomFragmentToDisplayPhotoFragment(selectedPhoto);
Navigation.findNavController(view).navigate(action);
Doing this,I can navigate to DisplayPhotoFragment.
But when I press back button of the device and also the Back arrow from the toolbar,it cant go back to CreateRoomFragment.
I tried this,but still unable to back to previous fragment:
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(),
new OnBackPressedCallback(true) {
#Override
public void handleOnBackPressed() {
navController.navigateUp(); //I tried this
navController.popBackStack(R.id.createRoomFragment,false); //and also this
}
});
Main Problem now:
By using the code above,the screen didnt go back to previous Fragment(CreateRoomFragment).It still stuck in DisplayPhotoFragment,but at the same time,an API method in CreateRoomFragment onViewCreated section is being called.
What causing this? and how can I solve this problem?
I had the same problem. For me the issue was that I was using a LiveData boolean to decide when to go to the next fragment. When I then navigated back/up the boolean was still true so it would automatically navigate forward again.
Android maintains a back stack that contains the destinations you've visited. The first destination of your app is placed on the stack when the user opens the app. Each call to the navigate() method puts another destination on top of the stack. Tapping Up or Back calls the NavController.navigateUp() and NavController.popBackStack() methods, respectively, to remove (or pop) the top destination off of the stack.
NavController.popBackStack() returns a boolean indicating whether it successfully popped back to another destination. The most common case when this returns false is when you manually pop the start destination of your graph.
When the method returns false, NavController.getCurrentDestination() returns null. You are responsible for either navigating to a new destination or handling the pop by calling finish() on your Activity.
When navigating using an action, you can optionally pop additional destinations off of the back stack by using popUpTo and popUpToInclusive parameter of the action.
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (true == conditionForCustomAction) {
CustomActionHere()
} else NavHostFragment.findNavController(this#MyFragment).navigateUp();
}
}
requireActivity().onBackPressedDispatcher.addCallback(
this, onBackPressedCallback
)
...
}
The best solution for handling navigation using live data is to use the SingleLiveEvent.
You can always use this class which is an extension of MutableLiveData.
https://gist.githubusercontent.com/cdmunoz/ebe5c4104dadc2a461f512ea1ca71495/raw/a17f76754f86a4c0b1a6b43f5c6e6d179535e627/SingleLiveEvent.kt
For a detail run down of this check:
https://proandroiddev.com/singleliveevent-to-help-you-work-with-livedata-and-events-5ac519989c70
Had a similar issue. We still have multiple activities with nav component.
So imagine activity A -> activity B, activity B has its own nav and fragments. When the initial fragment tries to pop the back stack there is nowhere to pop back to and the nav controller does not know to finish the activity. So one solution I found was to do
if (!findNavController().popBackStack()) activity?.finish()
If nav controller can not pop back it will finish activity.
You can use MutableSharedFlow instead on MutableLiveData if you want to observe the Event only once.
in your viewModel:
private val _events = MutableSharedFlow<Event>()
val events = _events.asSharedFlow() // read-only public view
suspend fun postEvent() {
_events.emit(event) // suspends until subscribers receive it
}
In your Activity/Fragment class:
lifecycleScope.launchWhenStarted {
viewModel.events.collect {
}
}
viewModel.postEvent()
This will prevent observing data continuously when going back to fragment.
I am creating a new Android Project and soon android foldable devices will be launched. I have an Activity which has fragment called first fragment.
First Fragment has a button called first button which open second fragment which has a button called second and on click of second, third fragment opens.
Suppose user is in third fragment and user decides to unfold his device, will the user go back to fragment one or will he stay in fragment three. As far as I have understood from the Developer Summit, the activity will be destroyed and recreated when user unfolds his device so technically user goes backs to first fragment leading to poor user experience.
So my question is should I consider even using fragments?, If yes how to manage state so that user goes to the same fragment he was when he folds or unfolds his device.
Following is my code if I am changing fragments
private fun displayView(fragment: Fragment?, title: String) {
if (fragment != null) {
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
supportFragmentManager.beginTransaction()
.replace(R.id.framelayout_activity_main, fragment, title).commit()
}
}
In onCreate(), you only want to execute a FragmentTransaction if this activity is being
newly created, instead of being recreated from a configuration change. Or, more accurately,
you only want to execute a FragmentTransaction if you do not already have fragments in the state that you want them.
So, a typical approach is to see if you already have a fragment in your container:
override fun onCreate(state: Bundle) {
super.onCreate(state)
if (supportFragmentManager.findFragmentById(R.id.framelayout_activity_main) == null) {
// do something to show your fragment
}
// other good stuff goes here
}
On the first onCreate() invocation, findFragmentById() will return null, so you execute your code to display your first fragment. On a subsequent onCreate() invocation after a configuration change, Android will have already set up your fragment(s) for you by the time onCreate() is called. So, in that case, findFragmentById() will return something other than null, so you know that you already have a fragment in your container and do not need to do anything more.