Android detect when fragment is detached - android

I can easily detect when Fragments are attached to Activity via Activity.onAttachFragment()
But how can I detect in Activity that some Fragment is detached from activity?
There is no Activity.onDetachFragment()
Is subcclasing Fragment and write some code to notify Activity about that state is the only solution?

you can use interface for communicating between Fragment and Activity
something like this :
public Class MyFragment extends Fragment {
FragmentCommunicator communicator;
public void setCommunicator(FragmentCommunicator communicator) {
this.communicator = communicator;
}
#Override
public void OnDetach() {
communicator.fragmentDetached();
}
...
public Interface FragmentCommunicator {
public void fragmentDetached();
}
}
and in your activity :
public Class MyActivity extends Activity Implements FragmentCommunicator {
...
MyFragment fragment = new MyFragment();
fragment.setCommunicator(this);
...
#Override
public void fragmentDetached() {
//Do what you want!
}
}
Edit:
the new approach is setting interface instance in onAttach.
public void onAttach(Activity activity) {
if (activity instanceof FragmentCommunicator) {
communicator = activity;
} else {
throw new RuntimeException("activity must implement FragmentCommunicator");
}
}
now there is no need to have setCommunicator method.

Mohammad's original answer is close to I would do. He has since updated it to leverage a mechanism provided by Android - Fragment.onAttach(Context context).In that approach, the fragment grabs components (ie, the activity) from the system and calls into it. This breaks inversion of control.
Here is my preferred approach:
public class MyActivity extends AppCompatActivity {
#Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
if (fragment instanceof MyFragment) {
((MyFragment) fragment).setListener(mMyFragmentListener);
}
}
private final MyFragment.Listener mMyFragmentListener = new MyFragment.Listener() {
#Override
public void onDetached(MyFragment fragment) {
fragment.setListener(null);
}
// implement other worker methods.
};
}
public class MyFragment extends Fragment {
#Nullable
private Listener mListener;
public void setListener(#Nullable Listener listener) {
mListener = listener;
}
public interface Listener {
void onDetached(MyFragment fragment);
// declare more worker methods here that leverage the connection.
}
#Override
public void onDetach() {
super.onDetach();
if (mListener != null) {
mListener.onDetached(this);
}
}
}
In this solution, the fragment doesn't dictate it's surroundings. Some control is given the to fragment in that it breaks the connection itself. We also already don't own the detaching of the fragment anyways, so clearing the listener is really just cleanup.
Here is an alternative approach that is more explicit, less prone to developer error, but also creates extra boiler plate (I prefer the previous approach because the goodbye handshake feels like an unnecessary distraction):
public static class MyActivity extends AppCompatActivity {
#Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
if (fragment instanceof MyFragment) {
((MyFragment) fragment).setListener(mMyFragmentListener);
}
}
private final MyFragment.Listener mMyFragmentListener = new MyFragment.Listener() {
#Override
public void onDetached(MyFragment fragment) {
fragment.setListener(null);
}
// implement other worker methods.
};
}
public static class MyFragment extends Fragment {
#Nullable
private Listener mListener;
public void setListener(#Nullable Listener listener) {
mListener = listener;
}
public interface Listener {
void onDetached(MyFragment fragment);
// declare more worker methods here that leverage the connection.
}
#Override
public void onDetach() {
super.onDetach();
if (mListener != null) {
mListener.onDetached(this);
}
}
}

You have a callback in the fragment life cycle. onDetach() is called when fragment is no longer attached to activity.

An alternative would be:
mFragmentManager.findFragmentByTag("Tag").getView()
If the view is null the fragment must be detached.

You can use ViewModel for update host activity. Shared ViewModel could be better choice than across the old listener based polymorphism model. You can follow the official documentation.
Data, fragment lifecyle etc. can be observable with shared viewmodel.
sealed class FragmentStates {
object Attached : FragmentStates()
object Started : FragmentStates()
object Stopped : FragmentStates()
object DeAtached : FragmentStates()
}
class FragmentStateViewModel : ViewModel() {
private val _fragmentState = MutableLiveData<FragmentStates>()
val fragmentStates: LiveData<FragmentStates> get() = _fragmentState
fun fragmentAttached() {
_fragmentState.value = FragmentStates.Attached
}
fun fragmentDeAtached() {
_fragmentState.value = FragmentStates.DeAtached
}
}
class HostActivity : AppCompatActivity() {
private val fragmentStateViewModel: FragmentStateViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fragmentStateViewModel.fragmentStates.observe(this, Observer {
when(it) {
FragmentStates.Attached -> {}
FragmentStates.Started -> {}
FragmentStates.Stopped -> {}
FragmentStates.DeAtached -> {}
}
})
}
}
class MyFragment: Fragment() {
private val fragmentStateViewModel: FragmentStateViewModel by activityViewModels()
override fun onAttach(context: Context) {
super.onAttach(context)
fragmentStateViewModel.fragmentAttached()
}
override fun onDetach() {
super.onDetach()
fragmentStateViewModel.fragmentDeAtached()
}
}

Related

How can a BottomSheetDialogFragment communicate with its host fragment?

I have a button in my fragment which opens a BottomSheetDialogFragment. I want to notify the host fragment if the user selected an item on the BottomSheetDialogFragment. In order to achieve this, I have made an interface in my BottomSheetDialogFragment. However, that interface only communicates with the host activity, not the fragment. How can I send the information from the dialog to the fragment?
This is my interface:
public interface BottomSheetListener {
void onButtonClicked(int index);
}
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
try {
mListener = (BottomSheetListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement BottomSheetListener");
}
}
getParentFragment will return the parent fragment, if the current fragment is attached to a fragment else it will return null if it is attached directly to an Activity
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
try {
mListener = (BottomSheetListener) getParentFragment();
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement BottomSheetListener");
}
}
When you use a lot of fragments, nested fragments or dialogfragments it becomes messy for communicate between them. I am suggesting to use ViewModel with LiveData for passing and updating data.
first add this to build gradle :
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
then create ViewModel class :
public class YourViewModel extends AndroidViewModel {
private MutableLiveData<Integer> yourMutableLiveData=new MutableLiveData<>();
public YourViewModel(#NonNull Application application) {
super(application);
}
public MutableLiveData<Integer> getYourMutableLiveData() {
return yourMutableLiveData;
}
}
This the fragment you want set value :
public class FragmentA extends Fragment{
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
YourViewModel yourViewModel =new ViewModelProvider(getActivity()).get(YourViewModel.class);
yourViewModel.getYourMutableLiveData().setValue(0);
}
}
And this is the fragment you want to get value when updated :
public class FragmentB extends Fragment{
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
YourViewModel yourViewModel =new ViewModelProvider(getActivity()).get(YourViewModel.class);
yourViewModel.getYourMutableLiveData().observe(getViewLifecycleOwner(), new Observer<Integer>() {
#Override
public void onChanged(Integer integer) {
}
});
}
}
It can work on dialog fragment as well as I tested.
Notes :
-Do not pass context or any view into view model.
-Remember that onActivityCreated comes after onCreateView.
-Do not set this key to
YourViewModel yourViewModel =new ViewModelProvider(this).get(YourViewModel.class);
in fragment if you want to pass data fragment to fragment but you can pass in activity.
-You can set more than one observer to the data.

Passing interface to Fragment

Let's consider a case where I have Fragment A and Fragment B.
B declares:
public interface MyInterface {
public void onTrigger(int position);
}
A implements this interface.
When pushing Fragment B into stack, how should I pass reference of Fragment A for it in Bundle so A can get the onTrigger callback when needed.
My use case scenario is that A has ListView with items and B has ViewPager with items. Both contain same items and when user goes from B -> A before popping B it should trigger the callback for A to update it's ListView position to match with B pager position.
Thanks.
Passing interface to Fragment
I think you are communicating between two Fragment
In order to do so, you can have a look into Communicating with Other Fragments
public class FragmentB extends Fragment{
MyInterface mCallback;
// Container Activity must implement this interface
public interface MyInterface {
public void onTrigger();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (MyInterface ) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement MyInterface ");
}
}
...
}
For Kotlin 1.0.0-beta-3595
interface SomeCallback {}
class SomeFragment() : Fragment(){
var callback : SomeCallback? = null //some might want late init, but I think this way is safer
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
callback = activity as? SomeCallback //returns null if not type 'SomeCallback'
return inflater!!.inflate(R.layout.frag_some_view, container, false);
}
}
It is optimal for two fragments to only communicate through an activity. So you can define an interface in Fragment B that is implemented in the activity. Then in the activity, define in the interface method what you want to happen in fragment A.
In Fragment B,
MyInterface mCallback;
public interface MyInterface {
void onTrigger(int position);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (MyInterface) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement MyInterface");
}
}
Method for determining if user goes from B to A
public void onChangeFragment(int position){
//other logic here
mCallback.onTrigger(position);
}
In Activity,
public void onTrigger(int position) {
//Find listview in fragment A
listView.smoothScrollToPosition(position);
}
Goodluck!
Using #Amit's answer, and adapting to the OPs question, here is all the relevant code:
public class FragmentA extends BaseFragment implements MyInterface {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// THIS IS JUST AN EXAMPLE OF WHERE YOU MIGHT CREATE FragmentB
FragmentB myFragmentB = new FragmentB();
}
void onTrigger(int position){
// My Callback Happens Here!
}
}
...
public class FragmentB extends BaseFragment {
private MyInterface callback;
public interface MyInterface {
void onTrigger(int position);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
callback = (MyInterface ) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement MyInterface");
}
}
}
I think you should use communication, as I've written below. This code comes from this Android Dev page of communication between Fragments:
HeadlinesFragment
public class HeadlinesFragment extends ListFragment {
OnHeadlineSelectedListener mCallback;
public void setOnHeadlineSelectedListener(Activity activity) {
mCallback = activity;
}
// Container Activity must implement this interface
public interface OnHeadlineSelectedListener {
public void onArticleSelected(int position);
}
// ...
}
MainActivity
public static class MainActivity extends Activity
implements HeadlinesFragment.OnHeadlineSelectedListener{
// ...
#Override
public void onAttachFragment(Fragment fragment) {
if (fragment instanceof HeadlinesFragment) {
HeadlinesFragment headlinesFragment = (HeadlinesFragment) fragment;
headlinesFragment.setOnHeadlineSelectedListener(this);
}
}
public static class MainActivity extends Activity
implements HeadlinesFragment.OnHeadlineSelectedListener {
...
public void onArticleSelected(int position) {
// The user selected the headline of an article from the HeadlinesFragment
// Do something here to display that article
}
You may create call back interface by this way.
var screenVisibility=activity as YourActivity
screenVisibility.setScreenVisibility("which screen you want")

Callback to a Fragment from a DialogFragment

Question: How does one create a callback from a DialogFragment to another Fragment. In my case, the Activity involved should be completely unaware of the DialogFragment.
Consider I have
public class MyFragment extends Fragment implements OnClickListener
Then at some point I could do
DialogFragment dialogFrag = MyDialogFragment.newInstance(this);
dialogFrag.show(getFragmentManager, null);
Where MyDialogFragment looks like
protected OnClickListener listener;
public static DialogFragment newInstance(OnClickListener listener) {
DialogFragment fragment = new DialogFragment();
fragment.listener = listener;
return fragment;
}
But there is no guarantee that the listener will be around if the DialogFragment pauses and resumes through its lifecycle. The only guarantees in a Fragment are those passed in through a Bundle via setArguments and getArguments.
There is a way to reference the activity if it should be the listener:
public Dialog onCreateDialog(Bundle bundle) {
OnClickListener listener = (OnClickListener) getActivity();
....
return new AlertDialog.Builder(getActivity())
........
.setAdapter(adapter, listener)
.create();
}
But I don't want the Activity to listen for events, I need a Fragment. Really, it could be any Java object that implements OnClickListener.
Consider the concrete example of a Fragment that presents an AlertDialog via DialogFragment. It has Yes/No buttons. How can I send these button presses back to the Fragment that created it?
Activity involved is completely unaware of the DialogFragment.
Fragment class:
public class MyFragment extends Fragment {
int mStackLevel = 0;
public static final int DIALOG_FRAGMENT = 1;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mStackLevel = savedInstanceState.getInt("level");
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("level", mStackLevel);
}
void showDialog(int type) {
mStackLevel++;
FragmentTransaction ft = getActivity().getFragmentManager().beginTransaction();
Fragment prev = getActivity().getFragmentManager().findFragmentByTag("dialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
switch (type) {
case DIALOG_FRAGMENT:
DialogFragment dialogFrag = MyDialogFragment.newInstance(123);
dialogFrag.setTargetFragment(this, DIALOG_FRAGMENT);
dialogFrag.show(getFragmentManager().beginTransaction(), "dialog");
break;
}
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch(requestCode) {
case DIALOG_FRAGMENT:
if (resultCode == Activity.RESULT_OK) {
// After Ok code.
} else if (resultCode == Activity.RESULT_CANCELED){
// After Cancel code.
}
break;
}
}
}
}
DialogFragment class:
public class MyDialogFragment extends DialogFragment {
public static MyDialogFragment newInstance(int num){
MyDialogFragment dialogFragment = new MyDialogFragment();
Bundle bundle = new Bundle();
bundle.putInt("num", num);
dialogFragment.setArguments(bundle);
return dialogFragment;
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.ERROR)
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(R.string.ok_button,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, getActivity().getIntent());
}
}
)
.setNegativeButton(R.string.cancel_button, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_CANCELED, getActivity().getIntent());
}
})
.create();
}
}
TargetFragment solution doesn't seem the best option for dialog fragments because it may create IllegalStateException after application get destroyed and recreated. In this case FragmentManager couldn't find the target fragment and you will get an IllegalStateException with a message like this:
"Fragment no longer exists for key android:target_state: index 1"
It seems like Fragment#setTargetFragment() is not meant for communication between a child and parent Fragment, but rather for communication between sibling-Fragments.
So alternative way is to create dialog fragments like this by using the ChildFragmentManager of the parent fragment, rather then using the activities FragmentManager:
dialogFragment.show(ParentFragment.this.getChildFragmentManager(), "dialog_fragment");
And by using an Interface, in onCreate method of the DialogFragment you can get the parent fragment:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
callback = (Callback) getParentFragment();
} catch (ClassCastException e) {
throw new ClassCastException("Calling fragment must implement Callback interface");
}
}
Only thing left is to call your callback method after these steps.
For more information about the issue, you can check out the link:
https://code.google.com/p/android/issues/detail?id=54520
I followed this simple steps to do this stuff.
Create interface like DialogFragmentCallbackInterface with some method like callBackMethod(Object data). Which you would calling to pass data.
Now you can implement DialogFragmentCallbackInterface interface in your fragment like MyFragment implements DialogFragmentCallbackInterface
At time of DialogFragment creation set your invoking fragment MyFragment as target fragment who created DialogFragment use myDialogFragment.setTargetFragment(this, 0) check setTargetFragment (Fragment fragment, int requestCode)
MyDialogFragment dialogFrag = new MyDialogFragment();
dialogFrag.setTargetFragment(this, 1);
Get your target fragment object into your DialogFragment by calling getTargetFragment() and cast it to DialogFragmentCallbackInterface.Now you can use this interface to send data to your fragment.
DialogFragmentCallbackInterface callback =
(DialogFragmentCallbackInterface) getTargetFragment();
callback.callBackMethod(Object data);
That's it all done! just make sure you have implemented this interface in your fragment.
Maybe a bit late, but may help other people with the same question like I did.
You can use setTargetFragment on Dialog before showing, and in dialog you can call getTargetFragment to get the reference.
A recommended approach is to use the new Fragment Result API.
By using it, you do not need to override onAttach(context) nor setTargetFragment(), which is now deprecated.
1 - Add a result listener on parent Fragment's onCreate:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
childFragmentManager.setFragmentResultListener("requestKey", this) { key, bundle ->
val result = bundle.getString("bundleKey")
}
}
2- On child Fragment, set the result (on a button click listener, for instance):
button.setOnClickListener {
val result = "resultSample"
setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}
More info on the docs: https://developer.android.com/guide/fragments/communicate#fragment-result
Hope it helps!
The Communicating with Other Fragments guide says the Fragments should communicate through the associated Activity.
Often you will want one Fragment to communicate with another, for
example to change the content based on a user event. All
Fragment-to-Fragment communication is done through the associated
Activity. Two Fragments should never communicate directly.
You should define an interface in your fragment class and implement that interface in its parent activity. The details are outlined here http://developer.android.com/guide/components/fragments.html#EventCallbacks . The code would look similar to:
Fragment:
public static class FragmentA extends DialogFragment {
OnArticleSelectedListener mListener;
// Container Activity must implement this interface
public interface OnArticleSelectedListener {
public void onArticleSelected(Uri articleUri);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnArticleSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
}
}
}
Activity:
public class MyActivity extends Activity implements OnArticleSelectedListener{
...
#Override
public void onArticleSelected(Uri articleUri){
}
...
}
According to the official documentation:
Fragment#setTargetFragment
Optional target for this fragment. This may be used, for example, if this fragment is being started by another, and when done wants to give a result back to the first. The target set here is retained across instances via FragmentManager#putFragment.
Fragment#getTargetFragment
Return the target fragment set by setTargetFragment(Fragment, int).
So you can do this:
// In your fragment
public class MyFragment extends Fragment implements OnClickListener {
private void showDialog() {
DialogFragment dialogFrag = MyDialogFragment.newInstance(this);
// Add this
dialogFrag.setTargetFragment(this, 0);
dialogFrag.show(getFragmentManager, null);
}
...
}
// then
public class MyialogFragment extends DialogFragment {
#Override
public void onAttach(Context context) {
super.onAttach(context);
// Then get it
Fragment fragment = getTargetFragment();
if (fragment instanceof OnClickListener) {
listener = (OnClickListener) fragment;
} else {
throw new RuntimeException("you must implement OnClickListener");
}
}
...
}
Update: Please note that there are easier ways of doing this using view models which I can share if someone is interested.
Kotlin guys here we go!
So the problem we have is that we created an activity, MainActivity, on that activity we created a fragment, FragmentA and now we want to create a dialog fragment on top of FragmentA call it FragmentB. How do we get the results from FragmentB back to FragmentA without going through MainActivity?
Note:
FragmentA is a child fragment of MainActivity. To manage fragments created in FragmentA we will use childFragmentManager which does that!
FragmentA is a parent fragment of FragmentB, to access FragmentA from inside FragmentB we will use parenFragment.
Having said that, inside FragmentA,
class FragmentA : Fragment(), UpdateNameListener {
override fun onSave(name: String) {
toast("Running save with $name")
}
// call this function somewhere in a clickListener perhaps
private fun startUpdateNameDialog() {
FragmentB().show(childFragmentManager, "started name dialog")
}
}
Here is the dialog fragment FragmentB.
class FragmentB : DialogFragment() {
private lateinit var listener: UpdateNameListener
override fun onAttach(context: Context) {
super.onAttach(context)
try {
listener = parentFragment as UpdateNameListener
} catch (e: ClassCastException) {
throw ClassCastException("$context must implement UpdateNameListener")
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
val binding = UpdateNameDialogFragmentBinding.inflate(LayoutInflater.from(context))
binding.btnSave.setOnClickListener {
val name = binding.name.text.toString()
listener.onSave(name)
dismiss()
}
builder.setView(binding.root)
return builder.create()
} ?: throw IllegalStateException("Activity can not be null")
}
}
Here is the interface linking the two.
interface UpdateNameListener {
fun onSave(name: String)
}
That's it.
The correct way of setting a listener to a fragment is by setting it when it is attached. The problem I had was that onAttachFragment() was never called. After some investigation I realised that I had been using getFragmentManager instead of getChildFragmentManager
Here is how I do it:
MyDialogFragment dialogFragment = MyDialogFragment.newInstance("title", "body");
dialogFragment.show(getChildFragmentManager(), "SOME_DIALOG");
Attach it in onAttachFragment:
#Override
public void onAttachFragment(Fragment childFragment) {
super.onAttachFragment(childFragment);
if (childFragment instanceof MyDialogFragment) {
MyDialogFragment dialog = (MyDialogFragment) childFragment;
dialog.setListener(new MyDialogFragment.Listener() {
#Override
public void buttonClicked() {
}
});
}
}
I was facing a similar problem. The solution that I found out was :
Declare an interface in your DialogFragment just like James McCracken has explained above.
Implement the interface in your activity (not fragment! That is not a good practice).
From the callback method in your activity, call a required public function in your fragment which does the job that you want to do.
Thus, it becomes a two-step process : DialogFragment -> Activity and then Activity -> Fragment
Full example how to use setFragmentResultListener:
Parent fragment MainFragment.kt:
import android.os.Bundle
import android.view.View
import android.widget.Button
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResult
import androidx.fragment.app.setFragmentResultListener
class MainFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val showDialogButton = view.findViewById<Button>(R.id.dialog_button)
showDialogButton.setOnClickListener {
showMyDialog()
}
}
private fun showMyDialog() {
MyDialogFragment.showOn(this) { bundle ->
/*here handle bundle result*/
}
}
}
your dialog:
import android.os.Bundle
import android.view.View
import android.widget.Button
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResult
import androidx.fragment.app.setFragmentResultListener
class MyDialogFragment : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val submitButton = view.findViewById<Button>(R.id.submitButton)
submitButton.setOnClickListener {
parentFragment?.setFragmentResult(KEY_CALLBACK_BUNDLE, buildResultBundle())
}
}
private fun buildResultBundle(): Bundle {
val bundle = Bundle()
/*here build your result bundle for parent fragment*/
return bundle
}
companion object {
const val TAG: String = "MyDialogFragment"
private const val KEY_CALLBACK_BUNDLE: String = "KEY_CALLBACK_BUNDLE"
fun showOn(fragment: Fragment, callback: (Bundle) -> Unit) {
val dialog = MyDialogFragment()
fragment.setFragmentResultListener(KEY_CALLBACK_BUNDLE) { requestKey: String, bundle: Bundle ->
if (requestKey == KEY_CALLBACK_BUNDLE) {
callback(bundle)
}
}
dialog.show(fragment.childFragmentManager, TAG)
}
}
}
I am getting result to Fragment DashboardLiveWall(calling fragment) from Fragment LiveWallFilterFragment(receiving fragment) Like this...
LiveWallFilterFragment filterFragment = LiveWallFilterFragment.newInstance(DashboardLiveWall.this ,"");
getActivity().getSupportFragmentManager().beginTransaction().
add(R.id.frame_container, filterFragment).addToBackStack("").commit();
where
public static LiveWallFilterFragment newInstance(Fragment targetFragment,String anyDummyData) {
LiveWallFilterFragment fragment = new LiveWallFilterFragment();
Bundle args = new Bundle();
args.putString("dummyKey",anyDummyData);
fragment.setArguments(args);
if(targetFragment != null)
fragment.setTargetFragment(targetFragment, KeyConst.LIVE_WALL_FILTER_RESULT);
return fragment;
}
setResult back to calling fragment like
private void setResult(boolean flag) {
if (getTargetFragment() != null) {
Bundle bundle = new Bundle();
bundle.putBoolean("isWorkDone", flag);
Intent mIntent = new Intent();
mIntent.putExtras(bundle);
getTargetFragment().onActivityResult(getTargetRequestCode(),
Activity.RESULT_OK, mIntent);
}
}
onActivityResult
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK) {
if (requestCode == KeyConst.LIVE_WALL_FILTER_RESULT) {
Bundle bundle = data.getExtras();
if (bundle != null) {
boolean isReset = bundle.getBoolean("isWorkDone");
if (isReset) {
} else {
}
}
}
}
}
Updated:
I made a library based on my gist code that generates those casting for you by using #CallbackFragment and #Callback.
https://github.com/zeroarst/callbackfragment.
And the example give you the example that send a callback from a fragment to another fragment.
Old answer:
I made a BaseCallbackFragment and annotation #FragmentCallback. It currently extends Fragment, you can change it to DialogFragment and will work. It checks the implementations with the following order: getTargetFragment() > getParentFragment() > context (activity).
Then you just need to extend it and declare your interfaces in your fragment and give it the annotation, and the base fragment will do the rest. The annotation also has a parameter mandatory for you to determine whether you want to force the fragment to implement the callback.
public class EchoFragment extends BaseCallbackFragment {
private FragmentInteractionListener mListener;
#FragmentCallback
public interface FragmentInteractionListener {
void onEcho(EchoFragment fragment, String echo);
}
}
https://gist.github.com/zeroarst/3b3f32092d58698a4568cdb0919c9a93
this is work for me
i think you can set callback in display method in your fragment,
**in my fragment**
val myDialogFragment=MyDialogFragment()
myDialogFragment.display(fragmentManager!!,this)
//my fragment implement CallbackDialogFragment so set this for display method
**in dialog fragment**
lateinit var callBackResult: CallbackDialogFragment
fun display(fragmentManager: FragmentManager, callback: CallbackDialogFragment) {
callBackResult = callback
show(fragmentManager,"dialogTag")
}
I solved this in an elegant way with RxAndroid. Receive an observer in the constructor of the DialogFragment and suscribe to observable and push the value when the callback being called. Then, in your Fragment create an inner class of the Observer, create an instance and pass it in the constructor of the DialogFragment. I used WeakReference in the observer to avoid memory leaks. Here is the code:
BaseDialogFragment.java
import java.lang.ref.WeakReference;
import io.reactivex.Observer;
public class BaseDialogFragment<O> extends DialogFragment {
protected WeakReference<Observer<O>> observerRef;
protected BaseDialogFragment(Observer<O> observer) {
this.observerRef = new WeakReference<>(observer);
}
protected Observer<O> getObserver() {
return observerRef.get();
}
}
DatePickerFragment.java
public class DatePickerFragment extends BaseDialogFragment<Integer>
implements DatePickerDialog.OnDateSetListener {
public DatePickerFragment(Observer<Integer> observer) {
super(observer);
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Use the current date as the default date in the picker
final Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH);
int day = c.get(Calendar.DAY_OF_MONTH);
// Create a new instance of DatePickerDialog and return it
return new DatePickerDialog(getActivity(), this, year, month, day);
}
#Override
public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
if (getObserver() != null) {
Observable.just(month).subscribe(getObserver());
}
}
}
MyFragment.java
//Show the dialog fragment when the button is clicked
#OnClick(R.id.btn_date)
void onDateClick() {
DialogFragment newFragment = new DatePickerFragment(new OnDateSelectedObserver());
newFragment.show(getFragmentManager(), "datePicker");
}
//Observer inner class
private class OnDateSelectedObserver implements Observer<Integer> {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Integer integer) {
//Here you invoke the logic
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
}
You can see the source code here: https://github.com/andresuarezz26/carpoolingapp
More improved way is to just use newInstanceand interface.
Here is a Fragment that needs DailogFragment
public class Fragment extends Fragment implements returnPinInterface {
....
....
public View onCreateView(#NotNull LayoutInflater inflater, ViewGroup
container,Bundle savedInstanceState) {
// A simple call to show DialogFragment
btnProceed.setOnClickListener(v -> {
fragment = DailogFragment.newInstance(this);
fragment.show(getChildFragmentManager(),null );
fragment.setCancelable(false);
});
//Grab whatever user clicked/selected/ or typed
#Override
public void onPinReturn(String s) {
Log.d("ReturnedPin", s);
}
}
Here comes your DialogFragment
public class PinDialogFragment extends DialogFragment {
//Create a static variable to help you receive instance of fragment you
//passed
public static Fragment fragm;
// Create new Instance and grab the object passed in Fragment up
//there
public static PinDialogFragment newInstance(Fragment frag) {
PinDialogFragment fragment = new PinDialogFragment();
fragm = frag;
return fragment;
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_pin, container, false);
//
btn.setOnClickListener(btn ->
listener.onReturnPin("1234"));
return v;
}
//Use the Fragm to instantiate your Interface
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (fragm instanceof ReturnPinInterface) {
listener = (ReturnPinInterface) fragm;
} else {
throw new RuntimeException("you must implement ReturnPinInterface");
}
}
}
Enjoy!
Using onAttach is advised in official Android documentation. So, we can make use of that method.
Make sure that your parent fragment implements a listener e.g. OnPopupButtonClickListener:
public interface OnPopupButtonClickListener {
void onPositiveButtonClicked();
void onNegativeButtonClicked();
}
In your parent fragment show your DialogFragment instance using getChildFragmentManager():
PopupDialogFragment dialogFragment = new PopupDialogFragment();
dialogFragment.show(getChildFragmentManager(), "PopupDialogFragment");
In your dialog class which extends DialogFragment instance add this method:
(Notice that we are retrieving our parent fragment via getParentFragment() which implements our custom listener interface OnPopupButtonClickListener
#Override
public void onAttach(#NonNull #NotNull Context context) {
super.onAttach(context);
// Verify that the host activity implements the callback interface
try {
listener = (OnPopupButtonClickListener) getParentFragment();
} catch (ClassCastException e) {
// The activity doesn't implement the interface, throw exception
throw new ClassCastException(getActivity().toString()
+ " must implement OnPopupButtonClickListener");
}
}
In your dialog you can then use your listener whenever it is needed in your DialogFragment, for example:
Button positiveButton = view.findViewById(R.id.positiveButton);
positiveButton.setOnClickListener(v -> {
if (listener != null) {
listener.onPositiveButtonClicked();
getDialog().dismiss();
}
});

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.

How to get LifecycleOwner in LifecycleObserver?

I need to get the LifecycleOwner in an LifecycleObserver to pass it into an ViewModel observer.
This is my MainActivity, were I add the LifecycleObserver.
public class MainActivity extends AppCompatActivity implements LifecycleOwner{
private LifecycleRegistry mLifecycleRegistry;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, MainFragment.newInstance())
.commitNow();
}
mLifecycleRegistry=new LifecycleRegistry(this);
mLifecycleRegistry.markState(Lifecycle.State.CREATED);
getLifecycle().addObserver(new MyLifecycleObserver());
}
#NonNull
#Override
public Lifecycle getLifecycle() {
return mLifecycleRegistry;
}
}
And this is my observere, where I need the LifecycleOwner.
public class MyLifecycleObserver implements LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onStartListener(){
FirebaseMassage.startFirebase();
MainFragment.massageViewModel.getMassage().observe(/*here I need the LifecycleOwner*/, textMassage -> {
FirebaseMassage.updateFirebaseMassage(textMassage);
});
}
}
You can just use another signature to get the LifecycleOwner like:
public class MyLifecycleObserver implements LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onStartListener(LifecycleOwner owner){
...
}
}
Observer methods can receive zero or one argument. If used(means you can go with zero argument too but IFF arguments are used), the first argument must be of type LifecycleOwner. Methods annotated with Lifecycle.Event.ON_ANY can receive the second argument, which must be of type Lifecycle.Event.
class TestObserver implements LifecycleObserver {
#OnLifecycleEvent(ON_CREATE)
void onCreated(LifecycleOwner source) {
//one argument possible
}
#OnLifecycleEvent(ON_START)
void onCreated() {
//no argument possible
}
#OnLifecycleEvent(ON_ANY)
void onAny(LifecycleOwner source, Event event) {
//two argument possible only for ON_ANY event
}
}
You shouldn't need to implement your own LifecycleRegistry - just use the one available from AppCompatActivity
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, MainFragment.newInstance())
.commitNow();
}
getLifecycle().addObserver(new MyLifecycleObserver());
}
}
If you separate the startFirebase call and the viewmodel observer you can observe the changes from the viewmodel directly in the fragment, i.e.
MyLifecycleObserver starts the firebase call when ON_START is emitted.
public class MyLifecycleObserver implements LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onStartListener(){
FirebaseMassage.startFirebase();
}
}
MainFragment observes the ViewModel directly.
public class MainFragment extends Fragment {
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
massageViewModel.getMassage().observe(this, textMassage -> {
FirebaseMassage.updateFirebaseMassage(textMassage);
});
}
Since #OnLifecycleEvent is deprecated, I believe the best approach would be to implement LifecycleObserver and override lifecycle methods:
class TestObserver: LifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
// your code
}
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
// your code
}
}
The official documentation of Abhishek Kumar's answer comes from here:
https://developer.android.com/reference/androidx/lifecycle/Lifecycle#subclasses-direct:~:text=Observer%20methods%20can%20receive%20zero%20or%20one%20argument
Here is the document itself:
Observer methods can receive zero or one argument. If used, the first argument must be of type LifecycleOwner.
Methods annotated with Lifecycle.Event.ON_ANY can receive the second argument, which must be of type Lifecycle.Event.
class TestObserver implements LifecycleObserver {
#OnLifecycleEvent(ON_CREATE)
void onCreated(LifecycleOwner source) {}
#OnLifecycleEvent(ON_ANY)
void onAny(LifecycleOwner source, Event event) {}
}

Categories

Resources