How to implement a backstack when using the KitKat Transitions Framework - android

I am using the new KitKat Transitions API on Android. I have created two Scene objects using two layouts. I animate from Scene 1 to Scene 2 inside a Fragment. I want to automatically move back to the previous Scene when the user presses the back button.
Is there some kind of built-in backstack mechanism when using Transitions, or do I have to roll my own?
It is easy enough to call TransitionManager.go(scene1), but I really do not want to implement an onBackPressed() listener in all my fragments that have Scene animations.

I ended up rolling my own solution.
Have your Activity implement this
public interface SceneBackstackHandler {
public void addBackstackListener(BackstackListener listener);
public void removeBackstackListener(BackstackListener listener);
public void removeAllBackstackListeners();
public interface BackstackListener {
public boolean onBackPressed();
}
}
Activity
private final Object mBackstackListenerLock = new Object();
private List<BackstackListener> mBackstackListeners = new ArrayList<>();
#Override
public void onBackPressed() {
synchronized (mBackstackListenerLock) {
for (BackstackListener mBackstackListener : mBackstackListeners) {
if (mBackstackListener.onBackPressed()) {
// handled by fragment
return;
}
}
super.onBackPressed();
}
}
#Override
protected void onPause() {
super.onPause();
removeAllBackstackListeners();
}
#Override
public void addBackstackListener(BackstackListener listener) {
synchronized (mBackstackListenerLock) {
mBackstackListeners.add(listener);
}
}
#Override
public void removeBackstackListener(BackstackListener listener) {
synchronized (mBackstackListenerLock) {
mBackstackListeners.remove(listener);
}
}
#Override
public void removeAllBackstackListeners() {
synchronized (mBackstackListenerLock) {
mBackstackListeners.clear();
}
}
Child Fragment:
public class MySceneFragment extends Fragment
implements SceneBackstackHandler.BackstackListener {
private Scene mCurrentScene;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mBackstackHandler = (SceneBackstackHandler) activity;
mBackstackHandler.addBackstackListener(this);
}
#Override
public void onDetach() {
super.onDetach();
mBackstackHandler.removeBackstackListener(this);
}
#Override
public boolean onBackPressed() {
if (mCurrentScene != null && mCurrentScene.equals(mMyScene)) {
removeMyScene();
return true;
}
return false;
}
private void changeScene(Scene scene) {
TransitionManager.go(scene);
mCurrentScene = scene;
}
}

I use an Otto event bus to communicate between my Activity and Fragments. The controlling Activity maintains its own Stack of custom back events which each contain a back action Runnable, i.e. what action should be taken when the back button is pressed.
The advantage to this approach is a slightly more decoupled design and should scale with more fragments. For readability, I have defined the Otto Events inside my Fragment, here, but these could be easily moved elsewhere in your project.
Here's some sample code to give you an idea of how it's done.
Fragment(s)
The Fragment signals its intent to take hold of the next back press by posting a BackStackRequestEvent to the Otto event bus and supplying a Runnable action to be executed when the event is popped off the Activity's custom stack. When the Fragment is detached, it sends a ClearBackStackEvent to the bus to remove any of the Fragment's back actions from the activity's custom stack.
public class MyFragment extends Fragment {
private final String BACK_STACK_ID = "MY_FRAGMENT";
...
public class BackStackRequestEvent {
private Runnable action;
private String id;
public BackStackRequestEvent(Runnable action, String id) {
this.action = action;
this.id = id;
}
public void goBack() {
action.run();
}
public String getId() {
return id;
}
}
public class ClearBackStackEvent {
private String id;
public ClearBackStackEvent(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
...
#Override
public void onDetach() {
super.onDetach();
// Get your Otto singleton and notify Activity that this
// Fragment's back actions are no longer needed
// The Fragment lifecycle stage in which you do this might vary
// based on your needs
EventBus.getInstance().post(new ClearBackStackEvent(BACK_STACK_ID));
}
...
public void someChangeInFragment() {
// Notify the Activity that we want to intercept the next onBackPressed
EventBus.getInstance().post(new BackStackRequestEvent(new Runnable()
{
#Override
public void run() {
// Reverse what we did
doBackAction();
}
}, BACK_STACK_ID)); // constant used later to remove items from Stack
}
}
Activity
The activity registers / unregisters its interest in the events we defined above in onStart() and onStop(). When it receives a new BackStackRequestEvent it adds it to its custom back stack. Once onBackPressed() is called, it pops the back stack and invokes the back action using BackStackRequestEvent.goBack() which in turn runs the Fragment's Runnable. If there is nothing on the Stack, the normal back behaviour is followed.
When the Fragment is detached, the Activity receives a ClearBackStackEvent and it removes all items of the supplied id from the Stack.
public class MyActivity extends Activity {
private Stack<MyFragment.BackStackRequestEvent> customBackStack = new Stack<>();
...
#Override
protected void onStart() {
super.onStart();
EventBus.getInstance().register(this);
}
#Override
protected void onStop() {
super.onStop();
EventBus.getInstance().unregister(this);
}
#Subscribe // Annotation indicating that we want to intercept this Otto event
public void backStackRequested(MyFragment.BackStackRequestEvent request) {
customBackStack.push(request);
}
#Override
public void onBackPressed() {
if (customBackStack.empty()) {
// No custom actions so default behaviour followed
super.onBackPressed();
}
else {
// Pop the custom action and call its goBack() action
MyFragment.BackStackRequestEvent back = customBackStack.pop();
back.goBack();
}
}
#Subscribe
public void clearBackStackRequested(MyFragment.ClearBackStackEvent request) {
String id = request.getId();
for (MyFragment.BackStackRequestEvent backItem : customBackStack) {
if (backItem.getId().contentEquals(id)) {
customBackStack.remove(backItem);
}
}
}
}

Related

Multiple Interfaces in Single Fragment

I have a Fragment that needs to communicate more than one Action back to it's Activity. For example,
When a button is clicked, it needs to communicate the onClick back to the Activity.
2.When a user's login and password match, a boolean value is sent to the Activity notifying it to start an Intent.
My first question is, is this common where a Fragment needs to relay more that one type of Action back to the Activity? And secondly, how is this solved? Is the following a good way to do it...
I created a custom class, which extends Fragment and included the two interfaces that I need (One to pass the onClick back to the Activity and One to pass a boolean value):
public class CustomInterfaceFragment extends Fragment {
public OnClickedListener listener;
public LogInInterface loggedInListener;
static interface OnClickedListener{
public void buttonClicked(View v);
}
static interface LogInInterface{
public void userLoggedIn(boolean loggedIn);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
this.listener = (OnClickedListener)activity;
this.loggedInListener = (LogInInterface)activity;
}}
I then extended this custom class in my Fragment and used the appropriate methods where needed. This is the onClick method in the Fragment...
#Override
public void onClick(View v) {
switch (v.getId()){
case R.id.register_button:{
listener.buttonClicked(v);//***Pass onClick Back to Activity
break;
}
case R.id.fragment_login_loginButton:{
ParseUser.logInInBackground(userName.getText().toString(), password.getText().toString(), new LogInCallback() {
#Override
public void done(ParseUser user, ParseException e) {
if (user!=null){
boolean verified = user.getBoolean("emailVerified");
if(!verified){
Toast.makeText(getActivity(),"Please Verify",Toast.LENGTH_LONG).show();
progressDialog.dismiss();
ParseUser.logOut();
}else{
progressDialog.dismiss();
loggedInListener.userLoggedIn(true);//***Pass boolean Back to Activity
}
}else {
Toast.makeText(getActivity(),e.getMessage(),Toast.LENGTH_LONG).show();
progressDialog.dismiss();
}
}
});
}
break;
}
}
Finally I implemented the custom fragment class and its interfaces in my Activity in order to retrieve the data.
Is this a reasonable way to solve this problem or am I missing something? The application seems to work fine. I just want to know what the best programming practice would be. Thank you.
all i can say is you can bring down this two interfaces to one like this below
public interface fragmentInteractions{
public void OnClickedListener(View v);
public void userLoggedIn(boolean loggedIn);
....
....
}
and i don't think the interface here needs to be static
Elaborating on Avinash Joshi's answer :
public interface CustomListener {
void onButtonClicked();
void onLoginResult( boolean isUserLoggedIn ); // You can pass User object via this method in case its required to do some operations
}
public class MainActivity extends Activity implements CustomListener {
#Override
public void onCreate( Bundle savedInstance ) {
// Initialize UI elements
// Initialize Fragment
}
#Override
public void onButtonClicked() {
//Action to be performed on button click
}
#Override
public void onLoginResult( boolean isUserLoggedIn ) {
if( isUserLoggedIn ) {
//take user to dashboard or any other screen
//Usually with the help of SupportFragmentManager
}
else {
//Take user to signup screen with an optional toast message
//In case parameters like User name and password need not be entered by user again, you can access them as function parameters and pass them to signupFragment via bundle
}
}
}
public class LoginFragment extends Fragment {
CustomListener mCustomListener;
#Override
public void onAttach( Context context ) {
super.onAttach( Context context );
try {
mCustomListner = (CustomListener) context;
} catch ( ClassCastException e {
Log.e(TAG, "Activity must implement CustomListener")
}
}
//Rest of Fragment initialization code here
}
Here's a complete example :
http://www.truiton.com/2015/12/android-activity-fragment-communication/

Model View Presenter with an EventBus, how to get Events back to Presenter?

I am using the Model-View-Presenter design pattern coupled with an EventBus (Otto). The entire reason I implemented this pattern is to decouple events to the presenter only, and have the presenter update the views.
This is an example of some of the code I have, I'll use getting Events as an example. (Please note that Events is different from the EventBus Event, meaning an Event in Events is an event like "Dad's Birthday", but an Event in the EventBus is a Bus-event).
Fragment
public class EventFragment extends Fragment {
private EventPresenter mEventPresenter;
// Initialize boilerplate code...
private void init() {
mEventPresenter = new EventPresenter();
mEventPresenter.loadEvents();
}
// I WANT TO MOVE THESE SUBSCRIPTION METHODS TO
// MY PRESENTER OR SUBSCRIBER, BUT THEY ARE
// COUPLED TO THE ADAPTER OR A VIEW
#Subscribe
public void onEventsLoaded(EventsLoaded eventsLoaded) {
List<Event> events = eventsLoaded.getEvents();
mAdapter.setEvents(events);
}
#Subscribe
public void onEventJoined(EventJoined eventJoined) {
mItemView.doAnimation();
mTextView.setText("Leave");
mAdapter.joinEvent(eventJoined.getEvent());
}
#Subscribe
public void onLeaveEvent(LeftEvent leftEvent) {
mItemView.doAnimation();
mTextView.setText("Join");
mAdapter.leftEvent(leftEvent.getEvent());
}
}
Presenter
public class EventPresenter {
// This is the only method I have right now kind of defeats the purpose of
// having a presenter
public void loadEvents() {
EventBus.getInstance().post(new LoadEvents());
}
}
Subscriber
public class EventSubscriber extends Subscriber {
// This class is registered on the event bus
#Subscribe
public void onLoadEvents(LoadEvents loadEvents) {
sClient.getEvents(new Callback<List<Event>>() {
#Override
public void onSuccess(List<Event> events, Response response) {
EventBus.post(new EventsLoaded(events));
}
#Override
public void onFailure(.....) {
// Handle failure
}
};
}
}
How can I get the Presenters and the Subscribers to handle all the business logic, and have the Fragment only handle views?
This is not the best possibility but May Be help you out.
subscribing events in presenter , with an instance of view inside it can help. Like presenter gets events from eventbus and call appropriate view method to update the UI.
Mind that I have not changed method names of your fragment that updates UI and have passed event object directly from presenter which is indeed now a subscriber of events of eventbus. You can change it accordingly.
Fragment---
public class EventFragment extends Fragment {
private EventPresenter mEventPresenter;
// Initialize boilerplate code...
#Override
public void onResume()
{
mEventPresenter.onResume();
}
#Override
public void onPause()
{
mEventPresenter.onPause();
}
private void init() {
mEventPresenter = new EventPresenter(this);
mEventPresenter.loadEvents();
}
// I WANT TO MOVE THESE SUBSCRIPTION METHODS TO
// MY PRESENTER OR SUBSCRIBER, BUT THEY ARE
// COUPLED TO THE ADAPTER OR A VIEW
public void onEventsLoaded(EventsLoaded eventsLoaded) {
List<Event> events = eventsLoaded.getEvents();
mAdapter.setEvents(events);
}
public void onEventJoined(EventJoined eventJoined) {
mItemView.doAnimation();
mTextView.setText("Leave");
mAdapter.joinEvent(eventJoined.getEvent());
}
public void onLeaveEvent(LeftEvent leftEvent) {
mItemView.doAnimation();
mTextView.setText("Join");
mAdapter.leftEvent(leftEvent.getEvent());
}
}
Presenter----
public class EventPresenter {
private EventFragment targetView;
public EventPresenter(EventFragment myView)
{
targetView = myView;
}
// This is the only method I have right now kind of defeats the purpose of
// having a presenter
public void loadEvents() {
EventBus.getInstance().post(new LoadEvents());
}
#Subscribe
public void onEventsLoaded(EventsLoaded eventsLoaded) {
targetView.onEventsLoaded(eventsLoaded);
}
#Subscribe
public void onEventJoined(EventJoined eventJoined) {
targetView.onEventJoined(eventJoined);
}
#Subscribe
public void onLeaveEvent(LeftEvent leftEvent) {
targetView.onLeaveEvent(leftEvent);
}
public void onResume()
{
//subscride event bus
}
public void onPause()
{
//unsubscride event bus
}
}
This is responsability to presenters to send or receive events and after to notify fragment or activities

Event Bus Fragment Unregister

I've a splashscreen Fragment that is registered to event bus:
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
If the screen goes autolock (or any other event that could call onStop), the container activity goes onStop and the fragment is no more capable to receive the (network) event. I'm thinking about moving "unregister" logic to onDestroy method. Is it a good idea?
You can move the event bus events to onStart() and onStop(), BUT you should know that certain methods cannot be called after onSaveInstanceState() (for example, dismissing a DialogFragment crashes you out).
So while this example was for Otto, I switched it to EventBus, and I personally have a wrapper that stores events while the application is paused.
public enum SingletonBus {
INSTANCE;
private static String TAG = SingletonBus.class.getSimpleName();
private final Vector<Object> eventQueueBuffer = new Vector<>();
private boolean paused;
public <T> void post(final T event) {
if (paused) {
eventQueueBuffer.add(event);
} else {
EventBus.getDefault().post(event);
}
}
public <T> void register(T subscriber) {
EventBus.getDefault().register(subscriber);
}
public <T> void unregister(T subscriber) {
EventBus.getDefault().unregister(subscriber);
}
public boolean isPaused() {
return paused;
}
public void setPaused(boolean paused) {
this.paused = paused;
if (!paused) {
Iterator<Object> eventIterator = eventQueueBuffer.iterator();
while (eventIterator.hasNext()) {
Object event = eventIterator.next();
post(event);
eventIterator.remove();
}
}
}
}
Then in a BaseActivity of sorts,
public class BaseActivity extends AppCompatActivity {
#Override
public void onPostResume() {
super.onPostResume();
SingletonBus.INSTANCE.setPaused(false);
}
#Override
public void onPause() {
SingletonBus.INSTANCE.setPaused(true);
super.onPause();
}
}
I recently had a similar issue.
First I used Otto and then switched to greenrobot's EventBus.
With EventBus you can register your objects sticky. EventBus.getDefault().registerSticky(this)
That is, this object will automatically receive every sticky event it is waiting for after registration. These events then have to be posted sticky. (EventBus.getDefault().postSticky(event))
Consider to remove the sticky events if you don't need them anymore
(EventBus.getDefault().removeSticky(Event.class)).
So if you want to receive this event only once you should remove it.
Maybe this approach might fit your needs.

is there a way to create condition listener

is there a way to create a listener that activates an event under a certain condition(boolean)?
i tried reading about creating custom listeners using interfaces but i dont think it's the answer for my question.
right now in my app i write an if statement everywhere so if i could just create a listener for it, it would be much easier.
set_A==B_Listener(????? {//listener takes place if a==b
#Override
public boolean event(View v, MotionEvent e)
{
//do something
}
});
Create a class variable for your statement, than you can attach an OnChangeListener to your statement in the onCreate method of your Activity
public class DummyActivity extends Activity {
interface OnStateChangeListener{
public void onAttach(Activity activity);
public void onStateChange(boolean state);
}
private boolean state;
private OnStateChangeListener listener;
private YourClass stateChangedCallback;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
listener = new OnStateChangeListener() {
private Activity currentActivity;
#Override
public void onAttach(Activity activity) {
currentActivity = activity;
}
#Override
public void onStateChange(boolean state) {
if (((DummyActivity) this.currentActivity).state != state) {
stateChangedCallback.doSomething();
((DummyActivity) this.currentActivity).state = state;
}
}
};
}
private void yourFunction() {
boolean state = true;
listener.onStateChange(state);
}
}

How to implement onBackPressed() in Fragments?

Is there a way in which we can implement onBackPressed() in Android Fragment similar to the way in which we implement in Android Activity?
As the Fragment lifecycle do not have onBackPressed(). Is there any other alternative method to over ride onBackPressed() in Android 3.0 fragments?
I solved in this way override onBackPressed in the Activity. All the FragmentTransaction are addToBackStack before commit:
#Override
public void onBackPressed() {
int count = getSupportFragmentManager().getBackStackEntryCount();
if (count == 0) {
super.onBackPressed();
//additional code
} else {
getSupportFragmentManager().popBackStack();
}
}
In my opinion the best solution is:
JAVA SOLUTION
Create simple interface :
public interface IOnBackPressed {
/**
* If you return true the back press will not be taken into account, otherwise the activity will act naturally
* #return true if your processing has priority if not false
*/
boolean onBackPressed();
}
And in your Activity
public class MyActivity extends Activity {
#Override public void onBackPressed() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_container);
if (!(fragment instanceof IOnBackPressed) || !((IOnBackPressed) fragment).onBackPressed()) {
super.onBackPressed();
}
} ...
}
Finally in your Fragment:
public class MyFragment extends Fragment implements IOnBackPressed{
#Override
public boolean onBackPressed() {
if (myCondition) {
//action not popBackStack
return true;
} else {
return false;
}
}
}
KOTLIN SOLUTION
1 - Create Interface
interface IOnBackPressed {
fun onBackPressed(): Boolean
}
2 - Prepare your Activity
class MyActivity : AppCompatActivity() {
override fun onBackPressed() {
val fragment =
this.supportFragmentManager.findFragmentById(R.id.main_container)
(fragment as? IOnBackPressed)?.onBackPressed()?.not()?.let {
super.onBackPressed()
}
}
}
3 - Implement in your target Fragment
class MyFragment : Fragment(), IOnBackPressed {
override fun onBackPressed(): Boolean {
return if (myCondition) {
//action not popBackStack
true
} else {
false
}
}
}
If you're using androidx.appcompat:appcompat:1.1.0 or above then you can add an OnBackPressedCallback to your fragment as follows
requireActivity()
.onBackPressedDispatcher
.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
Log.d(TAG, "Fragment back pressed invoked")
// Do custom work here
// if you want onBackPressed() to be called as normal afterwards
if (isEnabled) {
isEnabled = false
requireActivity().onBackPressed()
}
}
}
)
See https://developer.android.com/guide/navigation/navigation-custom-back
According to #HaMMeRed answer here is pseudocode how should it works.
Lets say that your main activity is called BaseActivity which has child fragments (like in SlidingMenu lib example).
Here are the steps:
First we need create interface and class which implements its interface to have generic method
Create class interface OnBackPressedListener
public interface OnBackPressedListener {
public void doBack();
}
Create class which implements skills of OnBackPressedListener
public class BaseBackPressedListener implements OnBackPressedListener {
private final FragmentActivity activity;
public BaseBackPressedListener(FragmentActivity activity) {
this.activity = activity;
}
#Override
public void doBack() {
activity.getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
Since now, we will work on our code BaseActivity and its fragments
Create private listener on top of your class BaseActivity
protected OnBackPressedListener onBackPressedListener;
create method to set listener in BaseActivity
public void setOnBackPressedListener(OnBackPressedListener onBackPressedListener) {
this.onBackPressedListener = onBackPressedListener;
}
in override onBackPressed implement something like that
#Override
public void onBackPressed() {
if (onBackPressedListener != null)
onBackPressedListener.doBack();
else
super.onBackPressed();
in your fragment in onCreateView you should add our listener
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = getActivity();
((BaseActivity)activity).setOnBackPressedListener(new BaseBackPressedListener(activity));
View view = ... ;
//stuff with view
return view;
}
Voila, now when you click back in fragment you should catch your custom on back method.
This worked for me: https://stackoverflow.com/a/27145007/3934111
#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;
}
});
}
If you wanted that sort of functionality you would need to override it in your activity, and then add a YourBackPressed interface to all your fragments, which you call on the relevant fragment whenever the back button is pressed.
Edit: I'd like to append my previous answer.
If I were to do this today, I'd use a broadcast, or possibly a ordered broadcast if I expected other panels to update in unison to the master/main content panel.
LocalBroadcastManager in the Support Library can help with this, and you just send the broadcast in onBackPressed and subscribe in your fragments that care. I think that Messaging is a more decoupled implementation and would scale better, so it would be my official implementation recommendation now. Just use the Intent's action as a filter for your message. send your newly created ACTION_BACK_PRESSED, send it from your activity and listen for it in the relevant fragments.
None of that is easy to implement nor will it function in an optimal way.
Fragments have a method call onDetach that will do the job.
#Override
public void onDetach() {
super.onDetach();
PUT YOUR CODE HERE
}
THIS WILL DO THE JOB.
Google has released a new API to deal with onBackPressed in Fragment:
activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
}
})
Just add addToBackStack while you are transitioning between your fragments like below:
fragmentManager.beginTransaction().replace(R.id.content_frame,fragment).addToBackStack("tag").commit();
if you write addToBackStack(null) , it will handle it by itself but if you give a tag , you should handle it manually.
New and better approach: Following piece of code in a Fragment will help you to capture the back-press event.
JAVA
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
OnBackPressedCallback callback = new OnBackPressedCallback(true) {
#Override
public void handleOnBackPressed() {
Toast.makeText(mContext, "back pressed", Toast.LENGTH_SHORT).show();
// And when you want to go back based on your condition
if (yourCondition) {
this.setEnabled(false);
requireActivity().onBackPressed();
}
}
};
requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
}
Kotlin
activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
}
})
Using Navigation component you can do it like this:
Java
public class MyFragment extends Fragment {
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// This callback will only be called when MyFragment is at least Started.
OnBackPressedCallback callback = new OnBackPressedCallback(true /* enabled by default */) {
#Override
public void handleOnBackPressed() {
// Handle the back button event
}
});
requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
// The callback can be enabled or disabled here or in handleOnBackPressed()
}
...
}
Kotlin
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
}
...
}
since this question and some of the answers are over five years old, let me share my solution. This is a follow-up and modernization to the answer from #oyenigun
UPDATE:
At the bottom of this article, I added an alternative implementation using an abstract Fragment extension that won't involve the Activity at all, which would be useful for anyone with a more complex fragment hierarchy involving nested fragments that require different back behavior.
I needed to implement this because some of the fragments I use have smaller views that I would like to dismiss with the back button, such as small information views that pop up, etc, but this is good for anyone who needs to override the behavior of the back button inside fragments.
First, define an Interface
public interface Backable {
boolean onBackPressed();
}
This interface, which I call Backable (I'm a stickler for naming conventions), has a single method onBackPressed() that must return a boolean value. We need to enforce a boolean value because we will need to know if the back button press has "absorbed" the back event. Returning true means that it has, and no further action is needed, otherwise, false says that the default back action still must take place. This interface should be it's own file (preferably in a separate package named interfaces). Remember, separating your classes into packages is good practice.
Second, find the top fragment
I created a method that returns the last Fragment object in the back stack. I use tags... if you use ID's, make the necessary changes. I have this static method in a utility class that deals with navigation states, etc... but of course, put it where it best suits you. For edification, I've put mine in a class called NavUtils.
public static Fragment getCurrentFragment(Activity activity) {
FragmentManager fragmentManager = activity.getFragmentManager();
if (fragmentManager.getBackStackEntryCount() > 0) {
String lastFragmentName = fragmentManager.getBackStackEntryAt(
fragmentManager.getBackStackEntryCount() - 1).getName();
return fragmentManager.findFragmentByTag(lastFragmentName);
}
return null;
}
Make sure the back stack count is greater than 0, otherwise an ArrayOutOfBoundsException could be thrown at runtime. If it isn't greater than 0, return null. We'll check for a null value later...
Third, Implement in a Fragment
Implement the Backable interface in whichever fragment where you need to override the back button behavior. Add the implementation method.
public class SomeFragment extends Fragment implements
FragmentManager.OnBackStackChangedListener, Backable {
...
#Override
public boolean onBackPressed() {
// Logic here...
if (backButtonShouldNotGoBack) {
whateverMethodYouNeed();
return true;
}
return false;
}
}
In the onBackPressed() override, put whatever logic you need. If you want the back button to not pop the back stack (the default behavior), return true, that your back event has been absorbed. Otherwise, return false.
Lastly, in your Activity...
Override the onBackPressed() method and add this logic to it:
#Override
public void onBackPressed() {
// Get the current fragment using the method from the second step above...
Fragment currentFragment = NavUtils.getCurrentFragment(this);
// Determine whether or not this fragment implements Backable
// Do a null check just to be safe
if (currentFragment != null && currentFragment instanceof Backable) {
if (((Backable) currentFragment).onBackPressed()) {
// If the onBackPressed override in your fragment
// did absorb the back event (returned true), return
return;
} else {
// Otherwise, call the super method for the default behavior
super.onBackPressed();
}
}
// Any other logic needed...
// call super method to be sure the back button does its thing...
super.onBackPressed();
}
We get the current fragment in the back stack, then we do a null check and determine if it implements our Backable interface. If it does, determine if the event was absorbed. If so, we're done with onBackPressed() and can return. Otherwise, treat it as a normal back press and call the super method.
Second Option to not involve the Activity
At times, you don't want the Activity to handle this at all, and you need to handle it directly within the fragment. But who says you can't have Fragments with a back press API? Just extend your fragment to a new class.
Create an abstract class that extends Fragment and implements the View.OnKeyListner interface...
import android.app.Fragment;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
public abstract class BackableFragment extends Fragment implements View.OnKeyListener {
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view.setFocusableInTouchMode(true);
view.requestFocus();
view.setOnKeyListener(this);
}
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
onBackButtonPressed();
return true;
}
}
return false;
}
public abstract void onBackButtonPressed();
}
As you can see, any fragment that extends BackableFragment will automatically capture back clicks using the View.OnKeyListener interface. Just call the abstract onBackButtonPressed() method from within the implemented onKey() method using the standard logic to discern a back button press. If you need to register key clicks other than the back button, just be sure to call the super method when overriding onKey() in your fragment, otherwise you'll override the behavior in the abstraction.
Simple to use, just extend and implement:
public class FragmentChannels extends BackableFragment {
...
#Override
public void onBackButtonPressed() {
if (doTheThingRequiringBackButtonOverride) {
// do the thing
} else {
getActivity().onBackPressed();
}
}
...
}
Since the onBackButtonPressed() method in the super class is abstract, once you extend you must implement onBackButtonPressed(). It returns void because it just needs to perform an action within the fragment class, and does not need to relay the absorption of the press back to the Activity. Make sure you do call the Activity onBackPressed() method if whatever you're doing with the back button doesn't require handling, otherwise, the back button will be disabled... and you don't want that!
Caveats
As you can see, this sets the key listener to the root view of the fragment, and we'll need to focus it. If there are edit texts involved (or any other focus-stealing views) in your fragment that extends this class, (or other inner fragments or views that have the same), you'll need to handle that separately. There's a good article on extending an EditText to lose focus on a back press.
I hope someone finds this useful. Happy coding.
The solution is simple:
If you have a base fragment class that all fragments extend, then add this code to it's class, otherwise create such a base fragment class
/*
* called when on back pressed to the current fragment that is returned
*/
public void onBackPressed()
{
// add code in super class when override
}
In your Activity class, override onBackPressed as follows:
private BaseFragment _currentFragment;
#Override
public void onBackPressed()
{
super.onBackPressed();
_currentFragment.onBackPressed();
}
In your Fragment class, add your desired code:
#Override
public void onBackPressed()
{
setUpTitle();
}
In kotlin, it's way simplier.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
//
}
})
}
onBackPressed() cause Fragment to be detach from Activity.
According to #Sterling Diaz answer I think he is right. BUT some situation will be wrong. (ex. Rotate Screen)
So, I think we could detect whether isRemoving() to achieve goals.
You can write it at onDetach() or onDestroyView(). It is work.
#Override
public void onDetach() {
super.onDetach();
if(isRemoving()){
// onBackPressed()
}
}
#Override
public void onDestroyView() {
super.onDestroyView();
if(isRemoving()){
// onBackPressed()
}
}
Inside the fragment's onCreate method add the following:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
OnBackPressedCallback callback = new OnBackPressedCallback(true) {
#Override
public void handleOnBackPressed() {
//Handle the back pressed
}
};
requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
}
You should add interface to your project like below;
public interface OnBackPressed {
void onBackPressed();
}
And then, you should implement this interface on your fragment;
public class SampleFragment extends Fragment implements OnBackPressed {
#Override
public void onBackPressed() {
//on Back Pressed
}
}
And you can trigger this onBackPressed event under your activities onBackPressed event like below;
public class MainActivity extends AppCompatActivity {
#Override
public void onBackPressed() {
Fragment currentFragment = getSupportFragmentManager().getFragments().get(getSupportFragmentManager().getBackStackEntryCount() - 1);
if (currentFragment instanceof OnBackPressed) {
((OnBackPressed) currentFragment).onBackPressed();
}
super.onBackPressed();
}
}
If you use EventBus, it is probably a far more simpler solution :
In your Fragment :
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
EventBus.getDefault().register(this);
}
#Override
public void onDetach() {
super.onDetach();
EventBus.getDefault().unregister(this);
}
// This method will be called when a MessageEvent is posted
public void onEvent(BackPressedMessage type){
getSupportFragmentManager().popBackStack();
}
and in your Activity class you can define :
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
// This method will be called when a MessageEvent is posted
public void onEvent(BackPressedMessage type){
super.onBackPressed();
}
#Override
public void onBackPressed() {
EventBus.getDefault().post(new BackPressedMessage(true));
}
BackPressedMessage.java is just a POJO object
This is super clean and there is no interface/implementation hassle.
Well I done it like this, and it work for me
Simple interface
FragmentOnBackClickInterface.java
public interface FragmentOnBackClickInterface {
void onClick();
}
Example implementation
MyFragment.java
public class MyFragment extends Fragment implements FragmentOnBackClickInterface {
// other stuff
public void onClick() {
// what you want to call onBackPressed?
}
then just override onBackPressed in activity
#Override
public void onBackPressed() {
int count = getSupportFragmentManager().getBackStackEntryCount();
List<Fragment> frags = getSupportFragmentManager().getFragments();
Fragment lastFrag = getLastNotNull(frags);
//nothing else in back stack || nothing in back stack is instance of our interface
if (count == 0 || !(lastFrag instanceof FragmentOnBackClickInterface)) {
super.onBackPressed();
} else {
((FragmentOnBackClickInterface) lastFrag).onClick();
}
}
private Fragment getLastNotNull(List<Fragment> list){
for (int i= list.size()-1;i>=0;i--){
Fragment frag = list.get(i);
if (frag != null){
return frag;
}
}
return null;
}
this is my solution:
in MyActivity.java:
public interface OnBackClickListener {
boolean onBackClick();
}
private OnBackClickListener onBackClickListener;
public void setOnBackClickListener(OnBackClickListener onBackClickListener) {
this.onBackClickListener = onBackClickListener;
}
#Override
public void onBackPressed() {
if (onBackClickListener != null && onBackClickListener.onBackClick()) {
return;
}
super.onBackPressed();
}
and in Fragment:
((MyActivity) getActivity()).setOnBackClickListener(new MyActivity.OnBackClickListener() {
#Override
public boolean onBackClick() {
if (condition) {
return false;
}
// some codes
return true;
}
});
public class MyActivity extends Activity {
protected OnBackPressedListener onBackPressedListener;
public interface OnBackPressedListener {
void doBack();
}
public void setOnBackPressedListener(OnBackPressedListener onBackPressedListener) {
this.onBackPressedListener = onBackPressedListener;
}
#Override
public void onBackPressed() {
if (onBackPressedListener != null)
onBackPressedListener.doBack();
else
super.onBackPressed();
}
#Override
protected void onDestroy() {
onBackPressedListener = null;
super.onDestroy();
}
}
in your fragment add the following, dont forget to implement mainactivity's interface.
public class MyFragment extends Framgent implements MyActivity.OnBackPressedListener {
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
((MyActivity) getActivity()).setOnBackPressedListener(this);
}
#Override
public void doBack() {
//BackPressed in activity will call this;
}
}
This is just a small code that will do the trick:
getActivity().onBackPressed();
Hope it helps someone :)
You can use onBackPressedDispatcher of parent activity like this:
val backpress = requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, true) {
// here dispatcher works for any action when back pressed
}
you can also enable/disable backpress button from fragment any time like this:
backpress.isEnabled = true/false
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
//your code
}
#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) {
// handle back button
replaceFragmentToBackStack(getActivity(), WelcomeFragment.newInstance(bundle), tags);
return true;
}
return false;
}
});
}
Providing custom back navigation by handling onBackPressed is now more easy with callbacks inside the fragment.
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (true == conditionForCustomAction) {
myCustomActionHere()
} else NavHostFragment.findNavController(this#MyFragment).navigateUp();
}
}
requireActivity().onBackPressedDispatcher.addCallback(
this, onBackPressedCallback
)
...
}
If you want the default back action based on some condition, you can use:
NavHostFragment.findNavController(this#MyFragment).navigateUp();
UPDATE: OnBackPressedDispatcher should be used.
Guide how to use available at developer.android.com/guide/navigation/navigation-custom-back
You can register fragment in activity to handle back press:
interface BackPressRegistrar {
fun registerHandler(handler: BackPressHandler)
fun unregisterHandler(handler: BackPressHandler)
}
interface BackPressHandler {
fun onBackPressed(): Boolean
}
usage:
In Fragment:
private val backPressHandler = object : BackPressHandler {
override fun onBackPressed(): Boolean {
showClosingWarning()
return false
}
}
override fun onResume() {
super.onResume()
(activity as? BackPressRegistrar)?.registerHandler(backPressHandler)
}
override fun onStop() {
(activity as? BackPressRegistrar)?.unregisterHandler(backPressHandler)
super.onStop()
}
In Activity:
class MainActivity : AppCompatActivity(), BackPressRegistrar {
private var registeredHandler: BackPressHandler? = null
override fun registerHandler(handler: BackPressHandler) { registeredHandler = handler }
override fun unregisterHandler(handler: BackPressHandler) { registeredHandler = null }
override fun onBackPressed() {
if (registeredHandler?.onBackPressed() != false) super.onBackPressed()
}
}
How about using onDestroyView()?
#Override
public void onDestroyView() {
super.onDestroyView();
}
Just follow these steps:
Always while adding a fragment,
fragmentTransaction.add(R.id.fragment_container, detail_fragment, "Fragment_tag").addToBackStack(null).commit();
Then in the main activity, override onBackPressed()
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStack();
} else {
finish();
}
To handle the back button in your app,
Fragment f = getActivity().getSupportFragmentManager().findFragmentByTag("Fragment_tag");
if (f instanceof FragmentName) {
if (f != null)
getActivity().getSupportFragmentManager().beginTransaction().remove(f).commit()
}
That's it!
Very short and sweet answer:
getActivity().onBackPressed();
Explanation of whole scenario of my case:
I have FragmentA in MainActivity,
I am opening FragmentB from FragmentA (FragmentB is child or nested fragment of FragmentA)
Fragment duedateFrag = new FragmentB();
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.container_body, duedateFrag);
ft.addToBackStack(null);
ft.commit();
Now if you want to go to FragmentA from FragmentB you can simply put getActivity().onBackPressed(); in FragmentB.

Categories

Resources