I searched all over the web, couldn't find a good reference on how to call fragment from another fragment.
Fragment A -> Fragment B (fragment A calls fragment B after 3 seconds)
Well, first of all you need to consider that it's a very bad idea to keep somehow a direct reference from FragmentA to FragmentB. Why:
FragmentB may be recreated and you may keep a reference to an older reference of FragmentB. So you have a memory leak.
FragmentB may be not created, added or visible. So you would have a null/unusable reference.
For this reason you need to consider methods that base on sending messages from FragmentA to FragmentB. I see several options:
Send a broadcast message using a custom action from FragmentA. FragmentB registers itself as a receiver for this kind of message (in onCreate/onResume/onAttach and de-register in onDestroy/onPause/onDetach) and when the message arrives it can handle it. This is very suitable if you have no data to send from FragmentA to FragmentB or if you do these are primitive types or easy-to-implement Parcelables. Here's an example:
Have this in FragmentA:
private void sendMessageToFragmentB(String someData) {
Intent messageIntent = new Intent("com.your_package.A_TO_B_ACTION");
messageIntent.putExtra("DATA_VALUE", someData);
LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(messageIntent);
}
While in FragmentB you could have this:
public class FragmentB extends Fragment {
private BroadcastReceiver messagesFromAReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if ("com.your_package.A_TO_B_ACTION".equals(intent.getAction())) {
String dataFromA = intent.getStringExtra("DATA_VALUE");
dataFromAReceived(dataFromA);
}
}
};
protected void dataFromAReceived(String data) {
// here you have the data
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
IntentFilter messageFromAIntentFilter = new IntentFilter("com.your_package.A_TO_B_ACTION");
LocalBroadcastManager.getInstance(getActivity()).registerReceiver(messagesFromAReceiver,
messageFromAIntentFilter);
}
#Override
public void onDestroy() {
super.onDestroy();
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(messagesFromAReceiver);
}
}
Use the hosting activity as a proxy: The host activity implements some kind of interface defined in FragmentA and when requested it can search if it can find FragmentB and if so call some method in there. The advantage is that you can send any data, no matter its weight. The base idea is descrived in Android dev articles. To exemplify, you could have FragmentA as:
public class FragmentA extends Fragment {
public static interface CallerProxy {
public void sendCustomMessage(Object... dataParams);
}
private CallerProxy proxyActivity;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof CallerProxy) {
this.proxyActivity = (CallerProxy) activity;
}
}
#Override
public void onDetach() {
super.onDetach();
this.proxyActivity = null;
}
private void sendMessageToFragmentB(String someData) {
if (proxyActivity != null) {
// send whatever data
proxyActivity.sendCustomMessage(new Integer(1), new Object());
// or don't send anything ...
proxyActivity.sendCustomMessage();
}
}
}
The proxy activity would have at least these methods and signature:
public class MyProxyActivity extends FragmentActivity implements CallerProxy {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// call setContentView and then make sure you've added FragmentA and
// FragmentB.
}
#Override
public void sendCustomMessage(Object... dataParams) {
// FragmentB must be identified somehow, either by tag,
// either by id. Suppose you'll identify by tag. This means you've added
// it previously with this tag
Fragment fragment = getSupportFragmentManager().findFragmentByTag("FragmentB-TAG");
if (fragment != null) {
FragmentB fragB = (FragmentB) fragment;
fragB.dataFromAReceived(dataParams);
}
}
}
While in FragmentB all you need is a method that can be called with above sent parameters:
public void dataFromAReceived(Object ... data) {
// here you have the data
}
Use or implement some sort of event bus. Some general details here. For Android I remember that Otto event bus was very handy and easy to use. Here's a link with this. This is very similar to first option as you need anyway to register and un-register.
In the end it depends on what you need to send as a message, when should it be received and how flexible does it need to be. ... your choice!
Enjoy programming!
Fragments are not supposed to connect to each other directly, that may be your problem in finding a decent guide to do this.
Your approach makes the assumption that a fragment B will always be reachable (and ready) for a fragment A to interact, and that is actually not true, will kill the flexibility of your Fragment and will cause you problems in the future.
A better approach to interaction of Fragments is to talk only through interfaces that talk directly to a activity that can handle who is alive when where and should receive what.
-> http://developer.android.com/training/basics/fragments/index.html
This Android guide above, specifically on the last topic, shows you how to do this.
i hope this code help you..
in your first fragment add this code
onCreateView
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getActivity());
IntentFilter intentFilter = new IntentFilter("update");
// Here you can add additional actions which then would be received by the BroadcastReceiver
broadcastManager.registerReceiver(receiver, intentFilter);
#Override
public void onDestroyView() {
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(receiver);
super.onDestroyView();
}
private BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action != null && action.equals("update")) {
// perform your update
getOngoingOrderData();
}
}
};
in your second fragment add this code where you send broadcast..
Intent intent = new Intent("update");
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getActivity());
broadcastManager.sendBroadcast(intent);
Imagine, I have FragmentA from which I startDialogFragment (there are EditText in box). How to can I get back the value from the EditText to FragmentA? I try to make something like this, and this but I was not successful.
The Fragment.onActivityResult() method is useful in this situation. It takes getTargetRequestCode(), which is a code you set up between fragments so they can be identified. In addition, it takes a request code, normally just 0 if the code worked well, and then an Intent, which you can attach a string too, like so
Intent intent = new Intent();
intent.putExtra("STRING_RESULT", str);
Also, the setTargetFragment(Fragment, requestCode) should be used in the fragment that the result is being sent from to identify it. Overall, you would have code in the requesting fragment that looks like this:
FragmentManager fm = getActivity().getSupportFragmentManager();
DialogFragment dialogFragment = new DialogFragment();
dialogFragment.setTargetFragment(this, REQUEST_CODE);
dialogFragment.show();
The class to send data (the DialogFragment) would use this Fragment we just defined to send the data:
private void sendResult(int REQUEST_CODE) {
Intent intent = new Intent();
intent.putStringExtra(EDIT_TEXT_BUNDLE_KEY, editTextString);
getTargetFragment().onActivityResult(
getTargetRequestCode(), REQUEST_CODE, intent);
}
To receive the data, we use this type of class in the Fragment which initially started the DialogFragment:
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// Make sure fragment codes match up
if (requestCode == DialogFragment.REQUEST_CODE) {
String editTextString = data.getStringExtra(
DialogFragment.EDIT_TEXT_BUNDLE_KEY);
At this point, you have the string from your EditText from the DialogFragment in the parent fragment. Just use the sendResult(int) method in your TextChangeListener() anonymous class so that the text is sent when you need it.
Assume a situation that you are uploading some file to server , on clicking of upload button a dialog should open,prompting for title and optional tag.And the dialog itself containing 2 buttons say cancel and continue.
make the UI as you wish by using layout xml file.
then create one class that extending DialogFragment. inflate the layout and initialize views inside onCreateView() method.
Inside that class create one interface
public interface uploadDialogInterface
{
public void senddata(String title, String tag);
}
uploadDialogInterface interfaceObj;
String title="";
String tag=" ";
And the important thing is you need to override onAttach() method
#Override
public void onAttach(Context context) {
super.onAttach(context);
this.context=context;
interfaceObj= (uploadDialogInterface) getTargetFragment();
}
And in the on Button click call the interface method like
#Override
public void onClick(View v) {
int id=v.getId();
if(id== R.id.vB_fud_cancel)
{
dismiss();
}
else if(id== R.id.vB_fud_upload)
{
title=mVideotitle.getText().toString();
tag=mOptionaltag.getText().toString();
if(mVideotitle.getText().toString().isEmpty()) {
Snackbar.make(mVideotitle,"Please enter the video title", Snackbar.LENGTH_SHORT).show();
}else
{
interfaceObj.senddata(title,tag);
dismiss();
}
}
}
And inside the Fragment or activity from which you are launching the dialog should contain setTargetFragment attribute.
private void callUploadDialog()
{
UploadDialogFragment fragment = new UploadDialogFragment();
fragment.setTargetFragment(this, 0);
FragmentManager manager = getFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
ft.setCustomAnimations(R.anim.fade_in, R.anim.fade_in);
fragment.show(ft, "UploadDialogFragment");
fragment.setCancelable(false);
}
And finally you should implement the interface (that was declared inside the dialog fragment) and override the method
#Override
public void senddata(String title,String optionaltag) {
this.videoTitle=title;
this.optionalTag=optionaltag;
}
I think this post will be helpful for those who are using dialog fragment for the first time . I was struggled to find the solution . And hopefully this will solve someone's problem in the future.
(Sorry for the language)
One of the better and simpler ways to do this is using Android ViewModel.
This helps in easier sharing of data, without the need of sending any data across fragments. You could do this not only for DialogFragments, but also for normal Fragments.
Source: https://developer.android.com/topic/libraries/architecture/viewmodel
Here is what I did
My ViewModel looks as below
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.ViewModel;
public class PlayerViewModel extends ViewModel {
private final MutableLiveData<Player> selectedPlayer = new MutableLiveData<>();
public LiveData<Player> getSelectedPlayer() {
return selectedPlayer;
}
public void selectPlayer(Player player) {
selectedPlayer.setValue(player);
}
}
In the Fragment where I select a Player, I use the following code in the onCreate method to bind the ViewModel
playerViewModel = ViewModelProviders.of(getActivity()).get(PlayerViewModel.class);
When a specific Player is selected, use the following (You can use an ArrayAdapter, DialogFragment's selector or anything you want to display list of players)
playerViewModel = ViewModelProviders.of(getActivity()).get(PlayerViewModel.class);
And finally, in the fragment where you need to show the Player information, do the following in the onCreate method
PlayerViewModel model = ViewModelProviders.of(getActivity()).get(PlayerViewModel.class);
model.getSelectedPlayer().observe(this, new Observer<Player>() {
#Override
public void onChanged(#Nullable Player selPlayer) {
if (selPlayer != null)
player = selPlayer;
populateData();
}
});
You need to send the data from the dialog back to the activity via a callback method, then have the activity give that data back to the fragment you want it to go to. Just a quick example:
public void datFromDialog(String data){
MyFragment mf = (MyFragment)getFragmentManager().findFragmentById(r.id.frag);
mf.iWantNewData(data);
}
What you want, according to Android Developers...
This method ensures that the calling fragment implements the onChangeListener of the dialog.
FragmentA (calling fragment):
MyDialogFragment f = new MyDialogFragment();
Bundle args = new Bundle();
args.putString("data", data);
f.setArguments(args);
// Set the calling fragment for this dialog.
f.setTargetFragment(FragmentA.this, 0);
f.show(getActivity().getSupportFragmentManager(), "MyDialogFragment");
MyDialogFragment:
import android.support.v4.app.DialogFragment;
public class MyDialogFragment extends DialogFragment {
public OnChangeListener onChangeListener;
interface OnChangeListener{
void onChange(Data data);
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Get the calling fragment and ensure that it implements onChangeListener.
try {
onChangeListener = (OnChangeListener) getTargetFragment();
} catch (ClassCastException e) {
throw new ClassCastException(
"The calling Fragment must implement MyDialogFragment.onChangeListener");
}
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
.....
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
// Send the data to the calling fragment.
onChangeListener.onChange(data);
}
});
.....
}
}
dialogFragment.setTargetFragment is deprecated, see : doc
Instead of using a target fragment to pass results, the fragment requesting a result should use FragmentManager.setFragmentResultListener(String, LifecycleOwner, FragmentResultListener) to register a FragmentResultListener with a requestKey using its parent fragment manager. The fragment delivering a result should then call FragmentManager.setFragmentResult(String, Bundle) using the same requestKey. Consider using setArguments to pass the requestKey if you need to support dynamic request keys.
Here is a simple implementation :
Call from host Fragment
val dialog = MockDialog.newInstance(
"requestKey")
dialog.show(
childFragmentManager, MockDialog.TAG
)
In MockDialog (which extends DialogFragment):
dialog.setPositiveButton(R.string.dialog_yes) { _, _ ->
parentFragmentManager.setFragmentResult(
arguments!!.getString(DIALOG_REQUEST_PARAM)!!,// which is "requestKey"
//add data to bundle
bundleOf("result" to "any data")
)
}
Get result on host Fragment:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
childFragmentManager.setFragmentResultListener(
"requestKey", this
) { requestKey, result ->
// you data here
val data = result.getString("result", null)
}
}
Base-line : you need to pass your "requestKey" and pass it back to host-fragment
Good luck,'.
In my app I have two fragments say fragmentA and FragmentB. When I click on a button in fragmetA, a list is opened in fragmentB. Now when I select an item from list in fragmentB I want the result to be passed to fragmentA. I am using only one TabActivity for all fragments. When list item is selected in fragmentB I am popping out fragmentB from stack so that I can directly go back to fragmentA.
Does anyone knows how to pass result to previous fragment.
Thanks.
Update
Activity is the parent controller and should take responsibility for handling those events raised by its fragments/views, which concern something outside of the scope of fragment/view itself.
A Fragment is to act as a sub-controller of Views it hosts. All the events and communication between its own views, the fragment should handle itself. When there is an event outside of a fragment's scope and responsibilities (like sending data to another fragment), that event should be escalated to its parent controller, the Activity.
Old
From this tutorial : http://developer.android.com/guide/components/fragments.html#CommunicatingWithActivity
Its better to let the activity apply changes to its fragment than passing values directly between fragments. Let your Activity implement a FragmentListener interface with onQuery(Bundle data) and onResult(Bundle data) methods.
Create a FragmentListener varaible in each of your fragments and then override onAttach() of each fragment as:
public void onAttach(Activity activity) {
super.onAttach(activity);
//---register parent activity for events---
try{
fragmentListener = (FragmentListener) activity;
}catch (ClassCastException e)
{
throw new ClassCastException("Parent activity must implement interface FragmentListener.");
}
}
This will enforce your child fragments to be automatically registered to parent Activity.
Also, remember to release fragmentListener reference in onDetach().
Now you can call your Activity from fragments.
On the other side, your Activity can always search for a fragment using getFragmentManager().findFragmentByTag("fragmentA") or findFragmentById("FragmentA").
If it can find your Fragment, Then it can cast it into your FragmentA class and call its methods. Same can be done with FragmentB or any other fragment..
One of the possible solutions:
public class DetachableResultReceiver extends ResultReceiver {
private Receiver mReceiver;
public DetachableResultReceiver(Handler handler) {
super(handler);
}
public void clearReceiver() {
mReceiver = null;
}
public void setReceiver(Receiver receiver) {
mReceiver = receiver;
}
public interface Receiver {
public void onReceiveResult(int resultCode, Bundle resultData);
}
#Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (mReceiver != null) {
mReceiver.onReceiveResult(resultCode, resultData);
}
}
}
First fragment implements DetachableResultReceiver.Receiver and creates DetachableResultReceiver object:
mReceiver = new DetachableResultReceiver(new Handler());
mReceiver.setReceiver(this);
Then you can pass it to second fragment arguments:
Bundle bundle = new Bundle();
bundle.putParcelable(Consts.EXTRA_RECEIVER, receiver);
fragment.setArguments(bundle);
And use it in second fragment:
receiver = getArguments().getParcelable(Consts.EXTRA_RECEIVER);
receiver.send(Consts.SOME_MESSAGE, someData);
In fragmentB.java set an OnClickListener to perform a method in the main class. Pass an arguement in fragmentB.java to the main class that is the variable, and handle the rest of it in your main class. Though fragments shouldn't really be dependent on activities at all. Fragments were made to plug and play anywhere.
This Example Shows EditTextListener:
myAwesomeActivity.java
fragmentA.java
fragmentB.java
fragmentB.java:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);
int x = 3;
EditText ed1 = (EditText) getView().findViewById(R.id.editText1);
ed1.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (MotionEvent.ACTION_UP == event.getAction()) {
((myAwesomeActivity) getActivity()).myMethod(x);
}
return false;
}
});
}
myAwesomeActivity.java:
publiv void myMethod (int x){
//Do whatever you want with variable
}
All you have to do is implement the correct type of listener, but the main point is shown. In one fragment activity, call a method and pass a variable to the main activity. From the main activity you can send it to your other fragment activity if you'd like.
You can also use SharedPreferences to save some string and after return back to the first fragment load it and clear.
I am using DialogFragments for a number of things: choosing item from list, entering text.
What is the best way to return a value (i.e. a string or an item from a list) back to the calling activity/fragment?
Currently I am making the calling activity implement DismissListener and giving the DialogFragment a reference to the activity. The Dialog then calls the OnDimiss method in the activity and the activity grabs the result from the DialogFragment object. Very messy and it doesn't work on configuration change (orientation change) as the DialogFragment loses the reference to the activity.
Thanks for any help.
Use myDialogFragment.setTargetFragment(this, MY_REQUEST_CODE) from the place where you show the dialog, and then when your dialog is finished, from it you can call getTargetFragment().onActivityResult(getTargetRequestCode(), ...), and implement onActivityResult() in the containing fragment.
It seems like an abuse of onActivityResult(), especially as it doesn't involve activities at all. But I've seen it recommended by official google people, and maybe even in the api demos. I think it's what g/setTargetFragment() were added for.
As you can see here there is a very simple way to do that.
In your DialogFragment add an interface listener like:
public interface EditNameDialogListener {
void onFinishEditDialog(String inputText);
}
Then, add a reference to that listener:
private EditNameDialogListener listener;
This will be used to "activate" the listener method(s), and also to check if the parent Activity/Fragment implements this interface (see below).
In the Activity/FragmentActivity/Fragment that "called" the DialogFragment simply implement this interface.
In your DialogFragment all you need to add at the point where you'd like to dismiss the DialogFragment and return the result is this:
listener.onFinishEditDialog(mEditText.getText().toString());
this.dismiss();
Where mEditText.getText().toString() is what will be passed back to the calling Activity.
Note that if you want to return something else simply change the arguments the listener takes.
Finally, you should check whether the interface was actually implemented by the parent activity/fragment:
#Override
public void onAttach(Context context) {
super.onAttach(context);
// Verify that the host activity implements the callback interface
try {
// Instantiate the EditNameDialogListener so we can send events to the host
listener = (EditNameDialogListener) context;
} catch (ClassCastException e) {
// The activity doesn't implement the interface, throw exception
throw new ClassCastException(context.toString()
+ " must implement EditNameDialogListener");
}
}
This technique is very flexible and allow calling back with the result even if your don;t want to dismiss the dialog just yet.
There is a much simpler way to receive a result from a DialogFragment.
First, in your Activity, Fragment, or FragmentActivity you need to add in the following information:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// Stuff to do, dependent on requestCode and resultCode
if(requestCode == 1) { // 1 is an arbitrary number, can be any int
// This is the return result of your DialogFragment
if(resultCode == 1) { // 1 is an arbitrary number, can be any int
// Now do what you need to do after the dialog dismisses.
}
}
}
The requestCode is basically your int label for the DialogFragment you called, I'll show how this works in a second. The resultCode is the code that you send back from the DialogFragment telling your current waiting Activity, Fragment, or FragmentActivity what happened.
The next piece of code to go in is the call to the DialogFragment. An example is here:
DialogFragment dialogFrag = new MyDialogFragment();
// This is the requestCode that you are sending.
dialogFrag.setTargetFragment(this, 1);
// This is the tag, "dialog" being sent.
dialogFrag.show(getFragmentManager(), "dialog");
With these three lines you are declaring your DialogFragment, setting a requestCode (which will call the onActivityResult(...) once the Dialog is dismissed, and you are then showing the dialog. It's that simple.
Now, in your DialogFragment you need to just add one line directly before the dismiss() so that you send a resultCode back to the onActivityResult().
getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, getActivity().getIntent());
dismiss();
That's it. Note, the resultCode is defined as int resultCode which I've set to resultCode = 1; in this case.
That's it, you can now send the result of your DialogFragment back to your calling Activity, Fragment, or FragmentActivity.
Also, it looks like this information was posted previously, but there wasn't a sufficient example given so I thought I'd provide more detail.
EDIT 06.24.2016
I apologize for the misleading code above. But you most certainly cannot receive the result back to the activity seeing as the line:
dialogFrag.setTargetFragment(this, 1);
sets a target Fragment and not Activity. So in order to do this you need to use implement an InterfaceCommunicator.
In your DialogFragment set a global variable
public InterfaceCommunicator interfaceCommunicator;
Create a public function to handle it
public interface InterfaceCommunicator {
void sendRequestCode(int code);
}
Then when you're ready to send the code back to the Activity when the DialogFragment is done running, you simply add the line before you dismiss(); your DialogFragment:
interfaceCommunicator.sendRequestCode(1); // the parameter is any int code you choose.
In your activity now you have to do two things, the first is to remove that one line of code that is no longer applicable:
dialogFrag.setTargetFragment(this, 1);
Then implement the interface and you're all done. You can do that by adding the following line to the implements clause at the very top of your class:
public class MyClass Activity implements MyDialogFragment.InterfaceCommunicator
And then #Override the function in the activity,
#Override
public void sendRequestCode(int code) {
// your code here
}
You use this interface method just like you would the onActivityResult() method. Except the interface method is for DialogFragments and the other is for Fragments.
For anyone still reading this: setTargetFragment() has been deprecated. It is now recommended to use the FragmentResultListener API like this:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setFragmentResultListener("requestKey") { key, bundle ->
val result = bundle.getString("resultKey")
// Do something with the result...
}
...
// Somewhere show your dialog
MyDialogFragment.newInstance().show(parentFragmentManager, "tag")
}
Then in your MyDialogFragment set the result:
button.setOnClickListener{
val result = "some string"
setFragmentResult("requestKey", bundleOf("resultKey" to result))
dismiss()
}
Well its too late may be to answer but here is what i did to get results back from the DialogFragment. very similar to #brandon's answer.
Here i am calling DialogFragment from a fragment, just place this code where you are calling your dialog.
FragmentManager fragmentManager = getFragmentManager();
categoryDialog.setTargetFragment(this,1);
categoryDialog.show(fragmentManager, "dialog");
where categoryDialog is my DialogFragment which i want to call and after this in your implementation of dialogfragment place this code where you are setting your data in intent. The value of resultCode is 1 you can set it or use system Defined.
Intent intent = new Intent();
intent.putExtra("listdata", stringData);
getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, intent);
getDialog().dismiss();
now its time to get back to to the calling fragment and implement this method. check for data validity or result success if you want with resultCode and requestCode in if condition.
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//do what ever you want here, and get the result from intent like below
String myData = data.getStringExtra("listdata");
Toast.makeText(getActivity(),data.getStringExtra("listdata"),Toast.LENGTH_SHORT).show();
}
Different approach, to allow a Fragment to communicate up to its Activity:
1) Define a public interface in the fragment and create a variable for it
public OnFragmentInteractionListener mCallback;
public interface OnFragmentInteractionListener {
void onFragmentInteraction(int id);
}
2) Cast the activity to the mCallback variable in the fragment
try {
mCallback = (OnFragmentInteractionListener) getActivity();
} catch (Exception e) {
Log.d(TAG, e.getMessage());
}
3) Implement the listener in your activity
public class MainActivity extends AppCompatActivity implements DFragment.OnFragmentInteractionListener {
//your code here
}
4) Override the OnFragmentInteraction in the activity
#Override
public void onFragmentInteraction(int id) {
Log.d(TAG, "received from fragment: " + id);
}
More info on it: https://developer.android.com/training/basics/fragments/communicating.html
One easy way I found was the following:
Implement this is your dialogFragment,
CallingActivity callingActivity = (CallingActivity) getActivity();
callingActivity.onUserSelectValue("insert selected value here");
dismiss();
And then in the activity that called the Dialog Fragment create the appropriate function as such:
public void onUserSelectValue(String selectedValue) {
// TODO add your implementation.
Toast.makeText(getBaseContext(), ""+ selectedValue, Toast.LENGTH_LONG).show();
}
The Toast is to show that it works. Worked for me.
I'm very surprised to see that no-one has suggested using local broadcasts for DialogFragment to Activity communication! I find it to be so much simpler and cleaner than other suggestions. Essentially, you register for your Activity to listen out for the broadcasts and you send the local broadcasts from your DialogFragment instances. Simple. For a step-by-step guide on how to set it all up, see here.
Or share ViewModel like showed here:
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// Update the UI.
});
}
}
https://developer.android.com/topic/libraries/architecture/viewmodel#sharing_data_between_fragments
In my case I needed to pass arguments to a targetFragment. But I got exception "Fragment already active". So I declared an Interface in my DialogFragment which parentFragment implemented. When parentFragment started a DialogFragment , it set itself as TargetFragment. Then in DialogFragment I called
((Interface)getTargetFragment()).onSomething(selectedListPosition);
In Kotlin
// My DialogFragment
class FiltroDialogFragment : DialogFragment(), View.OnClickListener {
var listener: InterfaceCommunicator? = null
override fun onAttach(context: Context?) {
super.onAttach(context)
listener = context as InterfaceCommunicator
}
interface InterfaceCommunicator {
fun sendRequest(value: String)
}
override fun onClick(v: View) {
when (v.id) {
R.id.buttonOk -> {
//You can change value
listener?.sendRequest('send data')
dismiss()
}
}
}
}
// My Activity
class MyActivity: AppCompatActivity(),FiltroDialogFragment.InterfaceCommunicator {
override fun sendRequest(value: String) {
// :)
Toast.makeText(this, value, Toast.LENGTH_LONG).show()
}
}
I hope it serves, if you can improve please edit it.
My English is not very good
if you want to send arguments and receive the result from second fragment, you may use Fragment.setArguments to accomplish this task
static class FirstFragment extends Fragment {
final Handler mUIHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 101: // receive the result from SecondFragment
Object result = msg.obj;
// do something according to the result
break;
}
};
};
void onStartSecondFragments() {
Message msg = Message.obtain(mUIHandler, 101, 102, 103, new Object()); // replace Object with a Parcelable if you want to across Save/Restore
// instance
putParcelable(new SecondFragment(), msg).show(getFragmentManager().beginTransaction(), null);
}
}
static class SecondFragment extends DialogFragment {
Message mMsg; // arguments from the caller/FirstFragment
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onViewCreated(view, savedInstanceState);
mMsg = getParcelable(this);
}
void onClickOK() {
mMsg.obj = new Object(); // send the result to the caller/FirstFragment
mMsg.sendToTarget();
}
}
static <T extends Fragment> T putParcelable(T f, Parcelable arg) {
if (f.getArguments() == null) {
f.setArguments(new Bundle());
}
f.getArguments().putParcelable("extra_args", arg);
return f;
}
static <T extends Parcelable> T getParcelable(Fragment f) {
return f.getArguments().getParcelable("extra_args");
}
TL;DR - use this AppDialog class to both pass data into DialogFragment as well as get result out of it.
Detailed explanation:
Premise - Fragments get destroyed and recreated on config changes. View models hang around. When using a Dialog, it is recommended to wrap it in DialogFragment so that when the user rotates device and changes orientation the Dialog will not unexpectedly disappear (the DialogFragment will re-create it and re-display it).
Limitation (hence this question) - The way the DialogFragment works is it takes a class that it will need to re-instantiate on configuration changes - that means one can't have constructor parameters to the subclass to pass parameters, and typically one needs to make custom callbacks through a view model to pass back result of dialog. That typically means a new subclass for every dialog.
The solution - To help with all this, this custom AppDialog fragment comes to the rescue - the parameters are stored in-memory (similar to view model, you can think of it as a tiny custom view model that holds T in memory and uses it to re-create the dialog on config changes) until the dialog fragment is dismissed. The proper way to call back would be through a view model. If the fragment that shows the AppDialog, then you probably already have a view model and you can reference it from the lambda used to create the dialog - that means additional strong reference to the view model until the dialog fragment is dismissed.
Example - see the examples where a simple Dialog is refactored to use this AppDialog utility class to both receive a parameter and do a callback to viewModel to notify of result.
The helper class:
class AppDialog<T>: DialogFragment() {
companion object {
fun<T> buildDialog(params: T? = null, builder: AppDialogLambda<T>): AppDialog<T> {
// Setup arguments
val args = Bundle()
args.putInt("key", pushDialogArgs(params, builder))
// Instantiate
val fragment = AppDialog<T>()
fragment.arguments = args
return fragment
}
// --------------------
// Dialog Arguments
private var lastKey: Int = 0
private val dialogArgs = mutableMapOf<Int, Pair<Any?, AppDialogLambda<*>>>()
private fun pushDialogArgs(params: Any?, builder: AppDialogLambda<*>): Int {
dialogArgs[lastKey] = params to builder
return lastKey++
}
private fun getDialogArgs(key: Int): Pair<Any?, AppDialogLambda<*>> {
return dialogArgs[key]!!
}
private fun deleteDialogArgs(key: Int) {
dialogArgs.remove(key)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Get arguments
val argKey = requireArguments().getInt("key")
val (params, builder) = getDialogArgs(argKey)
// We are getting back our arguments we passed AppDialog.buildDialog and
// the type is guaranteed to be the same. Silence this warning
#Suppress("UNCHECKED_CAST")
return (builder as AppDialogLambda<T>)(this, params as T?)
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
val argKey = requireArguments().getInt("key")
deleteDialogArgs(argKey)
}
}
Example usage (after):
val info = mapOf("message" to "${error.description}\n\nPlease check your Internet connection and try again.")
AppDialog.buildDialog(info) { fragment, params ->
fragment.isCancelable = false // since we are in a DialogFragment
AlertDialog.Builder(fragment.context)
.setTitle("Terms Of Service Failed To Load")
.setMessage(params!!["message"])
.setPositiveButton("Retry") { _, _ ->
// Update the view model instead of calling UserTOSFragment directly
// as the fragment may be destroyed and recreated
// on configuration changes. The viewModel will stay alive.
viewModel.onTermsOfServiceReload()
}
.setNegativeButton("Cancel") { _, _ ->
viewModel.onTermsOfServiceDeclined()
fragment.findNavController().popBackStack()
}.create()
}.show(parentFragmentManager, "TOS Failed Dialog")
Example usage (before):
Without using DialogFragment (for illustration purposes, don't do this, this is bad practice as the dialog will be destroyed on config changes), code inside UserTOSFragment.kt - note code used to call directly the UserTOSFragment.loadContent() on retry. This has to be rewritten to instead call viewModel.onTermsOfServiceDeclined() in the above example:
AlertDialog.Builder(context)
.setTitle("Terms Of Service Failed To Load")
.setMessage("${error.description}\n\nPlease check your Internet connection and try again.")
.setPositiveButton("Retry") { _, _ ->
loadContent()
}
.setCancelable(false)
.setNegativeButton("Cancel") { _, _ ->
viewModel.onTermsOfServiceDeclined()
findNavController().popBackStack()
}
.show()
On a dialog Fragment
class AbcDialogFragment(private val ondata: (data: String) -> Unit) : DialogFragment() {}
Code to show the dialog from fragment/Activity
val abcDialogFragment = AbcDialogFragment(ondata = {data-> })
abcDialogFragment.show(requireActivity().supportFragmentManager, "TAG")
and in the dialog fragment, you can invoke the onData when dialog fragment is closed or any click listeners.
Just to have it as one of the options (since no one mentioned it yet) - you could use an event bus like Otto.
So in the dialog you do:
bus.post(new AnswerAvailableEvent(42));
And have your caller (Activity or Fragment) subscribe to it:
#Subscribe public void answerAvailable(AnswerAvailableEvent event) {
// TODO: React to the event somehow!
}