Related
How can we get the onBackPressed callback only in the fragment class? Instead ,now it is going to the onBackPressed in the activity classes? I need to restrict sending the callback to Activity on backpress from fragment.
example::
#Override
public void onBackPressed() {
Log.d("SKT", "backPressHere");
}
Implement this in the Activity and restriction part you can handle via the interface.
The fragment itself does not have a return button, but instead you can put this code in the onBackPressed() method in the activity to which the fragment is attached, and with the getSupportFragmentManager() method you can control the examples of running fragments or delete and return do.
here is example :
#Override
public void onBackPressed() {
int count = getSupportFragmentManager().getBackStackEntryCount();
if (count == 0) {
// no fragment attach
super.onBackPressed();
} else {
// fragment attach
getSupportFragmentManager().popBackStack();
}
}
The fragment does not receive the onBackPressed callback. You should implement dispatching the event in the activity.
public interface BackPressedSupport{
/**
* return true if the fragment consumed the event. otherwise false.
*/
boolean onBackPressed();
}
In your Activity :
#Override
public void onBackPressed() {
Fragment f = getActiveFragment();
if (f instanceof BackPressedSupport && (BackPressedSupport f).onBackPressed()){
return;
}
super.onBackPressed();
// Other backPressed handling of activity.
}
public Fragment getActiveFragment() {
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
return null;
}
String tag = getSupportFragmentManager().getBackStackEntryAt(getSupportFragmentManager().getBackStackEntryCount() - 1).getName();
return getSupportFragmentManager().findFragmentByTag(tag);
}
In your Fragment:
public MyFragment extends Fragment implements BackPressedSupport {
/* Your code */
#Override
public boolean onBackPressed() {
/* Handle backpressed */
Log.d("SKT", "backPressHere");
return true ; // tell the activity to stop handling this backPressed event
// return false; // keep the default behaviour
}
}
I have a problem with the back/up button in a Fragment.
I have an Activity in which I have some fragments. In one Fragment, that I call "1", I have a list view. When I click on any item it goes to another fragment "2".
I need functionality such that the back/up button only works in Fragment 2 but not in Fragment 1.
Is there a way this can be done?
I have tried this in the Activity, but I don't understand how can this help:
#Override
public void onBackPressed() {
int count = getFragmentManager().getBackStackEntryCount();
if (count == 0) {
super.onBackPressed();
//additional code
} else {
getFragmentManager().popBackStack();
}
}
When i change from fragment 1 to fragment 2 i have this code:
fragment = new MaterialesFragment();
FragmentManager fragmentManager3 = getFragmentManager();
fragmentManager3.beginTransaction().replace(R.id.frame, fragment).addToBackStack("tagMateriales").commit();
Thanks a lot :-)
Try to use addToBackStack() method on the FragmentTransaction object you use to replace the fragment2 into the screen:
fragmentManager.beginTransaction().replace(R.id.content_frame,fragment2).addToBackStack("tagHere").commit();
You can pass null as the parameter if you don't need the actual tag.
Please use this code for your 'fragment 2'
public void showFragmentWithoutStack(Fragment fragment, String tag, int id) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
transaction.add(id, fragment, tag);
transaction.commitAllowingStateLoss();
}
Where tag might be String class name, id of your root view in activity (Because fragment should appear on the top layer).
There are a lot of way to do this job. In my opinion the most elegant is the following. I would isolate the logic in each component. Every fragment implements an interface like "IBackHandler" that allows activity and fragment to communicate. In the activity onBackPressed asks first the fragment (if it is instantiated) what to do and then acts.
The interface:
public interface IBackHandler {
boolean doBack();
}
The fragment that implements that interface and put its logic to handle the back button pressed:
public class YourFragment1 extends YourBaseFragment implements IBackHandler {
// your code
#Override
public boolean doBack() {
// return true means that you have done your stuff
//and you don't want 'super.onBackPressed()' to be called
return true; // this means do nothing
}
}
Fragment 2:
public class YourFragment2 extends YourBaseFragment implements IBackHandler {
// your code
#Override
public boolean doBack() {
return false; //this means: call 'super.onBackPressed()'
}
}
Your Activity:
public class YourActivity extends YourBaseActivity {
//your code
#Override
public void onBackPressed() {
Fragment currentFragment = getFragmentManager().findFragmentById(R.id.yourFragment);
if (currentFragment instanceof IBackHandler) {
if (!((IBackHandler) currentFragment).doBack()) {
super.onBackPressed();
}
} else
super.onBackPressed();
}
}
You could think that this is overengineering but I can reassure you that in this way you'll always be able to control what'happening in your flow.
Hope this help!
In order to use the onBackPressed() method override in the Activity as you have in the question, you also need to add this to your Fragment:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// Assuming MainActivity is the Activity subclass name
if (getActivity() instanceof MainActivity) {
((MainActivity) getActivity()).onBackPressed();
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
In order to show/hide the back button, add these methods to your Activity:
public void showUpButton() {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
public void hideUpButton() {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
}
Then, when you want to show or hide the back/up button from a Fragment, call these methods.
In your case, you could hide the back/up button from onResume() in Fragment 1:
((MainActivity) getActivity()).hideUpButton();
And then show it in onResume() in Fragment 2:
((MainActivity) getActivity()).showUpButton();
I solved this problem use this code when change fragment:
String fragmentIndexString="fragmenttwo";//or fragmentone something
transaction.addToBackStack();
This is full code in Activity:
FirstFragment firstFragment; // my first fragment
SecondFragment secondFragment; // my second fragment
public static String FIRST="asfasgasg"; //first fragment index
public static String SECOND="gsadgsagd"; //second fragment index
...
/* u can change fragment by using setFragment funiction */
public void setFragment(String page){
FragmentTransaction transaction = fragmentManager.beginTransaction();
switch (page){
case FIRST:
if(firstFragment==null) firstFragment= SettingFragment.newInstance();
if (!firstFragment.isAdded()) {
transaction.replace(R.id.fragmentcontainer, firstFragment);
transaction.addToBackStack(INDEX);
transaction.commit();
} else {
transaction.show(firstFragment);
}
break;
case SECOND:
if(secondFragment==null) secondFragment= SettingFragment.newInstance();
if (!secondFragment.isAdded()) {
transaction.replace(R.id.fragmentcontainer, secondFragment);
transaction.addToBackStack(INDEX);
transaction.commit();
} else {
transaction.show(secondFragment);
}
break;
default:
setFragment(INDEX);
}
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN){
if(fragmentManager.getBackStackEntryCount()>1){
fragmentManager.popBackStack();
return true;
}
}
return super.onKeyDown(keyCode, event);
}
I am working on a project and I need to be able to use the back button in each fragment to navigate between previous fragments, I have methods written to do so by using a back arrow in the action bar, however, I want to be able to use the same functionality on the back button pressed. I don't want to use the back stack. Is there a way to do this?
EDIT
Rather than using the back stack I want to be able to call the go back to previous method below when the user clicks the back button. I need to used the gobackpressed method within fragments. Is this possible? I hope this is clear and concise. Apologies for any confusion caused above.
Go Back to Previous
public void gobackToPreviousFragment(String preFragmentTag, Fragment preFragment){
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.setCustomAnimations(R.animator.close_slide_in,R.animator.close_slide_out);
ft.show(preFragment);
//**BY REMOVING FRAGMENT, WHEN USER TRIES TO REVISIT, FRAGMENT IS BLACK**
ft.remove(fm.findFragmentByTag(Misc.currentContentFragmentTag));
ft.addToBackStack(null);
ft.commit();
Misc.currentContentFragmentTag = preFragmentTag;
createBar(preFragment);
}
Go Forward
public void gotoNextFragment(String nextTag, Fragment nextFragment){
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.setCustomAnimations(R.animator.enter_slide_in, R.animator.enter_slide_out);
boolean newlyCreated = false;
if(nextFragment == null){
nextFragment = Fragment.instantiate(this, nextTag);
newlyCreated = true;
}
//hide current fragment
ft.hide(fm.findFragmentByTag(Misc.currentContentFragmentTag));
if(newlyCreated){
ft.add(R.id.content_frame, nextFragment, nextTag);
}
else{
ft.show(nextFragment);
}
ft.addToBackStack(null);
ft.commit();
Misc.currentContentFragmentTag = nextTag;
createBar(nextFragment);
}
These are how I navigate back and forth, and I'd like to be able to implement the go back method on the onBackPressed(). Does this make sense?
I didn't find any good answer about this problem, so this is my solution.
If you want to get backPress in each fragment do the following.
create interface OnBackPressedListener
public interface OnBackPressedListener {
void onBackPressed();
}
That each fragment that wants to be informed of backPress implements this interface.
In parent activity , you can override onBackPressed()
#Override
public void onBackPressed() {
List<Fragment> fragmentList = getSupportFragmentManager().getFragments();
if (fragmentList != null) {
//TODO: Perform your logic to pass back press here
for(Fragment fragment : fragmentList){
if(fragment instanceof OnBackPressedListener){
((OnBackPressedListener)fragment).onBackPressed();
}
}
}
}
Why don't you want to use the back stack? If there is an underlying problem or confusion maybe we can clear it up for you.
If you want to stick with your requirement just override your Activity's onBackPressed() method and call whatever method you're calling when the back arrow in your ActionBar gets clicked.
EDIT: How to solve the "black screen" fragment back stack problem:
You can get around that issue by adding a backstack listener to the fragment manager. That listener checks if the fragment back stack is empty and finishes the Activity accordingly:
You can set that listener in your Activity's onCreate method:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FragmentManager fm = getFragmentManager();
fm.addOnBackStackChangedListener(new OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if(getFragmentManager().getBackStackEntryCount() == 0) finish();
}
});
}
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
Log.w("a","")
}
})
In the fragment where you would like to handle your back button you should attach stuff to your view in the oncreateview
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.second_fragment, container, false);
v.setOnKeyListener(pressed);
return v;
}
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
// TODO Auto-generated method stub
if( keyCode == KeyEvent.KEYCODE_BACK ){
// back to previous fragment by tag
myfragmentclass fragment = (myfragmentclass) getActivity().getSupportFragmentManager().findFragmentByTag(TAG);
if(fragment != null){
(getActivity().getSupportFragmentManager().beginTransaction()).replace(R.id.cf_g1_mainframe_fm, fragment).commit();
}
return true;
}
return false;
}
};
This works for me :D
#Override
public void onResume() {
super.onResume();
if(getView() == null){
return;
}
getView().setFocusableInTouchMode(true);
getView().requestFocus();
getView().setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK){
// handle back button's click listener
return true;
}
return false;
}
});}
You can try to override onCreateAnimation, parameter and catch enter==false. This will fire before every back press.
#Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
if(!enter){
//leaving fragment
Log.d(TAG,"leaving fragment");
}
return super.onCreateAnimation(transit, enter, nextAnim);
}
For a Fragment you can simply add
getActivity().onBackPressed();
to your code
I found a new way to do it without interfaces. You only need to add the below code to the Fragment’s onCreate() method:
//overriding the fragment's oncreate
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//calling onBackPressedDispatcher and adding call back
requireActivity().onBackPressedDispatcher.addCallback(this) {
//do stuff here
}
}
Use this:
#Override
public void onBackPressed() {
int fragments = getFragmentManager().getBackStackEntryCount();
if (fragments == 1) {
finish();
}
super.onBackPressed();
}
This question already has answers here:
How to implement onBackPressed() in Fragments?
(58 answers)
Closed 6 years ago.
I have some fragments in my activity
[1], [2], [3], [4], [5], [6]
And on Back Button Press I must to return from [2] to [1] if current active fragment is [2], or do nothing otherwise.
What is the best practise to do that?
EDIT: Application must not return to [2] from [3]...[6]
When you are transitioning between Fragments, call addToBackStack() as part of your FragmentTransaction:
FragmentTransaction tx = fragmentManager.beginTransation();
tx.replace( R.id.fragment, new MyFragment() ).addToBackStack( "tag" ).commit();
If you require more detailed control (i.e. when some Fragments are visible, you want to suppress the back key) you can set an OnKeyListener on the parent view of your fragment:
//You need to add the following line for this solution to work; thanks skayred
fragment.getView().setFocusableInTouchMode(true);
fragment.getView().requestFocus();
fragment.getView().setOnKeyListener( new OnKeyListener()
{
#Override
public boolean onKey( View v, int keyCode, KeyEvent event )
{
if( keyCode == KeyEvent.KEYCODE_BACK )
{
return true;
}
return false;
}
} );
I'd rather do something like this:
private final static String TAG_FRAGMENT = "TAG_FRAGMENT";
private void showFragment() {
final Myfragment fragment = new MyFragment();
final FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment, fragment, TAG_FRAGMENT);
transaction.addToBackStack(null);
transaction.commit();
}
#Override
public void onBackPressed() {
final Myfragment fragment = (Myfragment) getSupportFragmentManager().findFragmentByTag(TAG_FRAGMENT);
if (fragment.allowBackPressed()) { // and then you define a method allowBackPressed with the logic to allow back pressed or not
super.onBackPressed();
}
}
if you overide the onKey method for the fragment view you're gonna need :
view.setFocusableInTouchMode(true);
view.requestFocus();
view.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
Log.i(tag, "keyCode: " + keyCode);
if( keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
Log.i(tag, "onKey Back listener is working!!!");
getFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
return true;
}
return false;
}
});
Use addToBackStack method when replacing one fragment by another:
getFragmentManager().beginTransaction().replace(R.id.content_frame, fragment).addToBackStack("my_fragment").commit();
Then in your activity, use the following code to go back from a fragment to another (the previous one).
#Override
public void onBackPressed() {
if (getParentFragmentManager().getBackStackEntryCount() > 0) {
getParentFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
If you want to handle hardware Back key event than you have to do following code in your onActivityCreated() method of Fragment.
You also need to check Action_Down or Action_UP event. If you will not check then onKey() Method will call 2 times.
Also, If your rootview(getView()) will not contain focus then it will not work. If you have clicked on any control then again you need to give focus to rootview using getView().requestFocus(); After this only onKeydown() will call.
getView().setFocusableInTouchMode(true);
getView().requestFocus();
getView().setOnKeyListener(new OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
Toast.makeText(getActivity(), "Back Pressed", Toast.LENGTH_SHORT).show();
return true;
}
}
return false;
}
});
Working very well for me.
Create interfaces:
BackButtonHandlerInterface
public interface BackButtonHandlerInterface {
void addBackClickListener (OnBackClickListener onBackClickListener);
void removeBackClickListener (OnBackClickListener onBackClickListener);
}
OnBackClickListener
public interface OnBackClickListener {
boolean onBackClick();
}
In Activity:
public class MainActivity extends AppCompatActivity implements BackButtonHandlerInterface {
private ArrayList<WeakReference<OnBackClickListener>> backClickListenersList = new ArrayList<>();
#Override
public void addBackClickListener(OnBackClickListener onBackClickListener) {
backClickListenersList.add(new WeakReference<>(onBackClickListener));
}
#Override
public void removeBackClickListener(OnBackClickListener onBackClickListener) {
for (Iterator<WeakReference<OnBackClickListener>> iterator = backClickListenersList.iterator();
iterator.hasNext();){
WeakReference<OnBackClickListener> weakRef = iterator.next();
if (weakRef.get() == onBackClickListener){
iterator.remove();
}
}
}
#Override
public void onBackPressed() {
if(!fragmentsBackKeyIntercept()){
super.onBackPressed();
}
}
private boolean fragmentsBackKeyIntercept() {
boolean isIntercept = false;
for (WeakReference<OnBackClickListener> weakRef : backClickListenersList) {
OnBackClickListener onBackClickListener = weakRef.get();
if (onBackClickListener != null) {
boolean isFragmIntercept = onBackClickListener.onBackClick();
if (!isIntercept) isIntercept = isFragmIntercept;
}
}
return isIntercept;
}
}
In Fragment:
public class MyFragment extends Fragment implements OnBackClickListener{
private BackButtonHandlerInterface backButtonHandler;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
backButtonHandler = (BackButtonHandlerInterface) activity;
backButtonHandler.addBackClickListener(this);
}
#Override
public void onDetach() {
super.onDetach();
backButtonHandler.removeBackClickListener(this);
backButtonHandler = null;
}
#Override
public boolean onBackClick() {
//This method handle onBackPressed()! return true or false
return false;
}
}
Update
Provide custom back navigation
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// This callback will only be called when MyFragment is at least Started.
val callback = requireActivity().onBackPressedDispatcher.addCallback(this) {
// Handle the back button event
}
// The callback can be enabled or disabled here or in the lambda
}
}
The most ideal way of doing this is found here:
Fragment: which callback invoked when press back button & customize it
public class MyActivity extends Activity
{
//...
//Defined in Activity class, so override
#Override
public void onBackPressed()
{
super.onBackPressed();
myFragment.onBackPressed();
}
}
public class MyFragment extends Fragment
{
//Your created method
public static void onBackPressed()
{
//Pop Fragments off backstack and do your other checks
}
}
#Override
public void onResume() {
super.onResume();
getView().setFocusableInTouchMode(true);
getView().requestFocus();
getView().setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK){
if (mDrawerLayout.isDrawerOpen(GravityCompat.START)){
mDrawerLayout.closeDrawer(GravityCompat.START);
}
return true;
}
return false;
}
});
}
After looking at all solutions, I realised there is a much simpler solution.
In your activity's onBackPressed() that is hosting all your fragments, find the fragment that you want to prevent back press. Then if found, just return. Then popBackStack will never happen for this fragment.
#Override
public void onBackPressed() {
Fragment1 fragment1 = (Fragment1) getFragmentManager().findFragmentByTag(“Fragment1”);
if (fragment1 != null)
return;
if (getFragmentManager().getBackStackEntryCount() > 0){
getFragmentManager().popBackStack();
}
}
We created tiny library for handling back press across multiple fragments and/or in Activity. Usage is as simple as adding dependency in your gradle file:
compile 'net.skoumal.fragmentback:fragment-back:0.1.0'
Let your fragment implement BackFragment interface:
public abstract class MyFragment extends Fragment implements BackFragment {
public boolean onBackPressed() {
// -- your code --
// return true if you want to consume back-pressed event
return false;
}
public int getBackPriority() {
return NORMAL_BACK_PRIORITY;
}
}
Notify your fragments about back presses:
public class MainActivity extends AppCompatActivity {
#Override
public void onBackPressed() {
// first ask your fragments to handle back-pressed event
if(!BackFragmentHelper.fireOnBackPressedEvent(this)) {
// lets do the default back action if fragments don't consume it
super.onBackPressed();
}
}
}
For more details and other use-cases visit GitHub page:
https://github.com/skoumalcz/fragment-back
Or you could use getSupportFragmentManager().getBackStackEntryCount() to check what to do:
#Override
public void onBackPressed() {
logger.d("###### back stack entry count : " + getSupportFragmentManager().getBackStackEntryCount());
if (getSupportFragmentManager().getBackStackEntryCount() != 0) {
// only show dialog while there's back stack entry
dialog.show(getSupportFragmentManager(), "ConfirmDialogFragment");
} else if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
// or just go back to main activity
super.onBackPressed();
}
}
If you manage the flow of adding to back stack every transaction, then you can do something like this in order to show the previous fragment when the user presses back button (you could map the home button too).
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0)
getFragmentManager().popBackStack();
else
super.onBackPressed();
}
For Those Who Use Static Fragment
In a case if you have a static fragment then It would be preferable.
Make an instance object of your fragment
private static MyFragment instance=null;
in onCreate() of MyFragment initialize that instance
instance=this;
also make a function to get Instance
public static MyFragment getInstance(){
return instance;
}
also make functions
public boolean allowBackPressed(){
if(allowBack==true){
return true;
}
return false;
}
//allowBack is a boolean variable that will be set to true at the action
//where you want that your backButton should not close activity. In my case I open
//Navigation Drawer then I set it to true. so when I press backbutton my
//drawer should be get closed
public void performSomeAction(){
//.. Your code
///Here I have closed my drawer
}
In Your Activity You can do
#Override
public void onBackPressed() {
if (MyFragment.getInstance().allowBackPressed()) {
MyFragment.getInstance().performSomeAction();
}
else{
super.onBackPressed();
}
}
Working Code:
package com.example.keralapolice;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentManager.OnBackStackChangedListener;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
public class ChiefFragment extends Fragment {
View view;
// public OnBackPressedListener onBackPressedListener;
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle args) {
view = inflater.inflate(R.layout.activity_chief, container, false);
getActivity().getActionBar().hide();
view.setFocusableInTouchMode(true);
view.requestFocus();
view.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
Log.i(getTag(), "keyCode: " + keyCode);
if (keyCode == KeyEvent.KEYCODE_BACK) {
getActivity().getActionBar().show();
Log.i(getTag(), "onKey Back listener is working!!!");
getFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
// String cameback="CameBack";
Intent i = new Intent(getActivity(), home.class);
// i.putExtra("Comingback", cameback);
startActivity(i);
return true;
} else {
return false;
}
}
});
return view;
}
}
I think the easiest way is to create an interface, and in the Activity check if the fragment is of the interface type, and if so, call its method to handle the pop. Here's the interface to implement in the fragment.
public interface BackPressedFragment {
// Note for this to work, name AND tag must be set anytime the fragment is added to back stack, e.g.
// getActivity().getSupportFragmentManager().beginTransaction()
// .replace(R.id.fragment_container, MyFragment.newInstance(), "MY_FRAG_TAG")
// .addToBackStack("MY_FRAG_TAG")
// .commit();
// This is really an override. Should call popBackStack itself.
void onPopBackStack();
}
Here's how to implement it.
public class MyFragment extends Fragment implements BackPressedFragment
#Override
public void onPopBackStack() {
/* Your code goes here, do anything you want. */
getActivity().getSupportFragmentManager().popBackStack();
}
And in your Activity, when you handle the pop (likely in both onBackPressed and onOptionsItemSelected), pop the backstack using this method:
public void popBackStack() {
FragmentManager fm = getSupportFragmentManager();
// Call current fragment's onPopBackStack if it has one.
String fragmentTag = fm.getBackStackEntryAt(fm.getBackStackEntryCount() - 1).getName();
Fragment currentFragment = getSupportFragmentManager().findFragmentByTag(fragmentTag);
if (currentFragment instanceof BackPressedFragment)
((BackPressedFragment)currentFragment).onPopBackStack();
else
fm.popBackStack();
}
I'm working with SlidingMenu and Fragment, present my case here and hope helps somebody.
Logic when [Back] key pressed :
When SlidingMenu shows, close it, no more things to do.
Or when 2nd(or more) Fragment showing, slide back to previous Fragment, and no more things to do.
SlidingMenu not shows, current Fragment is #0, do the original [Back] key does.
public class Main extends SherlockFragmentActivity
{
private SlidingMenu menu=null;
Constants.VP=new ViewPager(this);
//Some stuff...
#Override
public void onBackPressed()
{
if(menu.isMenuShowing())
{
menu.showContent(true); //Close SlidingMenu when menu showing
return;
}
else
{
int page=Constants.VP.getCurrentItem();
if(page>0)
{
Constants.VP.setCurrentItem(page-1, true); //Show previous fragment until Fragment#0
return;
}
else
{super.onBackPressed();} //If SlidingMenu is not showing and current Fragment is #0, do the original [Back] key does. In my case is exit from APP
}
}
}
This is a very good and reliable solution: http://vinsol.com/blog/2014/10/01/handling-back-button-press-inside-fragments/
The guy has made an abstract fragment that handles the backPress behaviour and is switching between the active fragments using the strategy pattern.
For some of you there maybe a little drawback in the abstract class...
Shortly, the solution from the link goes like this:
// Abstract Fragment handling the back presses
public abstract class BackHandledFragment extends Fragment {
protected BackHandlerInterface backHandlerInterface;
public abstract String getTagText();
public abstract boolean onBackPressed();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(!(getActivity() instanceof BackHandlerInterface)) {
throw new ClassCastException("Hosting activity must implement BackHandlerInterface");
} else {
backHandlerInterface = (BackHandlerInterface) getActivity();
}
}
#Override
public void onStart() {
super.onStart();
// Mark this fragment as the selected Fragment.
backHandlerInterface.setSelectedFragment(this);
}
public interface BackHandlerInterface {
public void setSelectedFragment(BackHandledFragment backHandledFragment);
}
}
And usage in the activity:
// BASIC ACTIVITY CODE THAT LETS ITS FRAGMENT UTILIZE onBackPress EVENTS
// IN AN ADAPTIVE AND ORGANIZED PATTERN USING BackHandledFragment
public class TheActivity extends FragmentActivity implements BackHandlerInterface {
private BackHandledFragment selectedFragment;
#Override
public void onBackPressed() {
if(selectedFragment == null || !selectedFragment.onBackPressed()) {
// Selected fragment did not consume the back press event.
super.onBackPressed();
}
}
#Override
public void setSelectedFragment(BackHandledFragment selectedFragment) {
this.selectedFragment = selectedFragment;
}
}
rootView.setFocusableInTouchMode(true);
rootView.requestFocus();
rootView.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
Fragment NameofFragment = new NameofFragment;
FragmentTransaction transaction=getFragmentManager().beginTransaction();
transaction.replace(R.id.frame_container,NameofFragment);
transaction.commit();
return true;
}
return false;
}
});
return rootView;
Add addToBackStack() to fragment transaction and then use below code for Implementing Back Navigation for Fragments
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
// Update your UI here.
}
});
if you are using FragmentActivity. then do like this
first call This inside your Fragment.
public void callParentMethod(){
getActivity().onBackPressed();
}
and then Call onBackPressed method in side your parent FragmentActivity class.
#Override
public void onBackPressed() {
//super.onBackPressed();
//create a dialog to ask yes no question whether or not the user wants to exit
...
}
You can use from getActionBar().setDisplayHomeAsUpEnabled() :
#Override
public void onBackStackChanged() {
int backStackEntryCount = getFragmentManager().getBackStackEntryCount();
if(backStackEntryCount > 0){
getActionBar().setDisplayHomeAsUpEnabled(true);
}else{
getActionBar().setDisplayHomeAsUpEnabled(false);
}
}
Add this code in your Activity
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() == 0) {
super.onBackPressed();
} else {
getFragmentManager().popBackStack();
}
}
And add this line in your Fragment before commit()
ft.addToBackStack("Any name");
in fragment class put this code for back event:
rootView.setFocusableInTouchMode(true);
rootView.requestFocus();
rootView.setOnKeyListener( new OnKeyListener()
{
#Override
public boolean onKey( View v, int keyCode, KeyEvent event )
{
if( keyCode == KeyEvent.KEYCODE_BACK )
{
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.frame_container, new Book_service_provider()).commit();
return true;
}
return false;
}
} );
Checking the backstack works perfectly
#Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if (keyCode == KeyEvent.KEYCODE_BACK)
{
if (getFragmentManager().getBackStackEntryCount() == 1)
{
// DO something here since there is only one fragment left
// Popping a dialog asking to quit the application
return false;
}
}
return super.onKeyDown(keyCode, event);
}
In your oncreateView() method you need to write this code and in KEYCODE_BACk condition you can write whatever the functionality you want
View v = inflater.inflate(R.layout.xyz, container, false);
//Back pressed Logic for fragment
v.setFocusableInTouchMode(true);
v.requestFocus();
v.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
getActivity().finish();
Intent intent = new Intent(getActivity(), MainActivity.class);
startActivity(intent);
return true;
}
}
return false;
}
});
I'm using the compatibility package to use Fragments with Android 2.2.
When using fragments, and adding transitions between them to the backstack, I'd like to achieve the same behavior of onResume of an activity, i.e., whenever a fragment is brought to "foreground" (visible to the user) after poping out of the backstack, I'd like some kind of callback to be activated within the fragment (to perform certain changes on a shared UI resource, for instance).
I saw that there is no built in callback within the fragment framework. is there s a good practice in order to achieve this?
For a lack of a better solution, I got this working for me:
Assume I have 1 activity (MyActivity) and few fragments that replaces each other (only one is visible at a time).
In MyActivity, add this listener:
getSupportFragmentManager().addOnBackStackChangedListener(getListener());
(As you can see I'm using the compatibility package).
getListener implementation:
private OnBackStackChangedListener getListener()
{
OnBackStackChangedListener result = new OnBackStackChangedListener()
{
public void onBackStackChanged()
{
FragmentManager manager = getSupportFragmentManager();
if (manager != null)
{
MyFragment currFrag = (MyFragment) manager.findFragmentById(R.id.fragmentItem);
currFrag.onFragmentResume();
}
}
};
return result;
}
MyFragment.onFragmentResume() will be called after a "Back" is pressed. few caveats though:
It assumes you added all
transactions to the backstack (using
FragmentTransaction.addToBackStack())
It will be activated upon each stack
change (you can store other stuff in
the back stack such as animation) so
you might get multiple calls for the
same instance of fragment.
I've changed the suggested solution a little bit. Works better for me like that:
private OnBackStackChangedListener getListener() {
OnBackStackChangedListener result = new OnBackStackChangedListener() {
public void onBackStackChanged() {
FragmentManager manager = getSupportFragmentManager();
if (manager != null) {
int backStackEntryCount = manager.getBackStackEntryCount();
if (backStackEntryCount == 0) {
finish();
}
Fragment fragment = manager.getFragments()
.get(backStackEntryCount - 1);
fragment.onResume();
}
}
};
return result;
}
After a popStackBack() you can use the following callback : onHiddenChanged(boolean hidden) within your fragment
The following section at Android Developers describes a communication mechanism Creating event callbacks to the activity. To quote a line from it:
A good way to do that is to define a callback interface inside the fragment and require that the host activity implement it. When the activity receives a callback through the interface, it can share the information with other fragments in the layout as necessary.
Edit:
The fragment has an onStart(...) which is invoked when the fragment is visible to the user. Similarly an onResume(...) when visible and actively running. These are tied to their activity counterparts.
In short: use onResume()
If a fragment is put on backstack, Android simply destroys its view. The fragment instance itself is not killed. A simple way to start should to to listen to the onViewCreated event, an put you "onResume()" logic there.
boolean fragmentAlreadyLoaded = false;
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState == null && !fragmentAlreadyLoaded) {
fragmentAlreadyLoaded = true;
// Code placed here will be executed once
}
//Code placed here will be executed even when the fragment comes from backstack
}
In my activity onCreate()
getSupportFragmentManager().addOnBackStackChangedListener(getListener());
Use this method to catch specific Fragment and call onResume()
private FragmentManager.OnBackStackChangedListener getListener()
{
FragmentManager.OnBackStackChangedListener result = new FragmentManager.OnBackStackChangedListener()
{
public void onBackStackChanged()
{
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (currentFragment instanceof YOURFRAGMENT) {
currentFragment.onResume();
}
}
};
return result;
}
A little improved and wrapped into a manager solution.
Things to keep in mind. FragmentManager is not a singleton, it manages only Fragments within Activity, so in every activity it will be new. Also, this solution so far doesn't take ViewPager into account that calls setUserVisibleHint() method helping to control visiblity of Fragments.
Feel free to use following classes when dealing with this issue (uses Dagger2 injection). Call in Activity:
//inject FragmentBackstackStateManager instance to myFragmentBackstackStateManager
FragmentManager fragmentManager = getSupportFragmentManager();
myFragmentBackstackStateManager.apply(fragmentManager);
FragmentBackstackStateManager.java:
#Singleton
public class FragmentBackstackStateManager {
private FragmentManager fragmentManager;
#Inject
public FragmentBackstackStateManager() {
}
private BackstackCallback backstackCallbackImpl = new BackstackCallback() {
#Override
public void onFragmentPushed(Fragment parentFragment) {
parentFragment.onPause();
}
#Override
public void onFragmentPopped(Fragment parentFragment) {
parentFragment.onResume();
}
};
public FragmentBackstackChangeListenerImpl getListener() {
return new FragmentBackstackChangeListenerImpl(fragmentManager, backstackCallbackImpl);
}
public void apply(FragmentManager fragmentManager) {
this.fragmentManager = fragmentManager;
fragmentManager.addOnBackStackChangedListener(getListener());
}
}
FragmentBackstackChangeListenerImpl.java:
public class FragmentBackstackChangeListenerImpl implements FragmentManager.OnBackStackChangedListener {
private int lastBackStackEntryCount = 0;
private final FragmentManager fragmentManager;
private final BackstackCallback backstackChangeListener;
public FragmentBackstackChangeListenerImpl(FragmentManager fragmentManager, BackstackCallback backstackChangeListener) {
this.fragmentManager = fragmentManager;
this.backstackChangeListener = backstackChangeListener;
lastBackStackEntryCount = fragmentManager.getBackStackEntryCount();
}
private boolean wasPushed(int backStackEntryCount) {
return lastBackStackEntryCount < backStackEntryCount;
}
private boolean wasPopped(int backStackEntryCount) {
return lastBackStackEntryCount > backStackEntryCount;
}
private boolean haveFragments() {
List<Fragment> fragmentList = fragmentManager.getFragments();
return fragmentList != null && !fragmentList.isEmpty();
}
/**
* If we push a fragment to backstack then parent would be the one before => size - 2
* If we pop a fragment from backstack logically it should be the last fragment in the list, but in Android popping a fragment just makes list entry null keeping list size intact, thus it's also size - 2
*
* #return fragment that is parent to the one that is pushed to or popped from back stack
*/
private Fragment getParentFragment() {
List<Fragment> fragmentList = fragmentManager.getFragments();
return fragmentList.get(Math.max(0, fragmentList.size() - 2));
}
#Override
public void onBackStackChanged() {
int currentBackStackEntryCount = fragmentManager.getBackStackEntryCount();
if (haveFragments()) {
Fragment parentFragment = getParentFragment();
//will be null if was just popped and was last in the stack
if (parentFragment != null) {
if (wasPushed(currentBackStackEntryCount)) {
backstackChangeListener.onFragmentPushed(parentFragment);
} else if (wasPopped(currentBackStackEntryCount)) {
backstackChangeListener.onFragmentPopped(parentFragment);
}
}
}
lastBackStackEntryCount = currentBackStackEntryCount;
}
}
BackstackCallback.java:
public interface BackstackCallback {
void onFragmentPushed(Fragment parentFragment);
void onFragmentPopped(Fragment parentFragment);
}
This is the correct answer you can call onResume() providing the fragment is attached to the activity. Alternatively you can use onAttach and onDetach
onResume() for the fragment works fine...
public class listBook extends Fragment {
private String listbook_last_subtitle;
...
#Override
public void onCreate(Bundle savedInstanceState) {
String thisFragSubtitle = (String) getActivity().getActionBar().getSubtitle();
listbook_last_subtitle = thisFragSubtitle;
}
...
#Override
public void onResume(){
super.onResume();
getActivity().getActionBar().setSubtitle(listbook_last_subtitle);
}
...
public abstract class RootFragment extends Fragment implements OnBackPressListener {
#Override
public boolean onBackPressed() {
return new BackPressImpl(this).onBackPressed();
}
public abstract void OnRefreshUI();
}
public class BackPressImpl implements OnBackPressListener {
private Fragment parentFragment;
public BackPressImpl(Fragment parentFragment) {
this.parentFragment = parentFragment;
}
#Override
public boolean onBackPressed() {
((RootFragment) parentFragment).OnRefreshUI();
}
}
and final extent your Frament from RootFragment to see effect
My workaround is to get the current title of the actionbar in the Fragment before setting it to the new title. This way, once the Fragment is popped, I can change back to that title.
#Override
public void onResume() {
super.onResume();
// Get/Backup current title
mTitle = ((ActionBarActivity) getActivity()).getSupportActionBar()
.getTitle();
// Set new title
((ActionBarActivity) getActivity()).getSupportActionBar()
.setTitle(R.string.this_fragment_title);
}
#Override
public void onDestroy() {
// Set title back
((ActionBarActivity) getActivity()).getSupportActionBar()
.setTitle(mTitle);
super.onDestroy();
}
I have used enum FragmentTags to define all my fragment classes.
TAG_FOR_FRAGMENT_A(A.class),
TAG_FOR_FRAGMENT_B(B.class),
TAG_FOR_FRAGMENT_C(C.class)
pass FragmentTags.TAG_FOR_FRAGMENT_A.name() as fragment tag.
and now on
#Override
public void onBackPressed(){
FragmentManager fragmentManager = getFragmentManager();
Fragment current
= fragmentManager.findFragmentById(R.id.fragment_container);
FragmentTags fragmentTag = FragmentTags.valueOf(current.getTag());
switch(fragmentTag){
case TAG_FOR_FRAGMENT_A:
finish();
break;
case TAG_FOR_FRAGMENT_B:
fragmentManager.popBackStack();
break;
case default:
break;
}