I'm migrating my dialogs, currently using Activity.showDialog(DIALOG_ID);
, to use the DialogFragment
system as discussed in the android reference.
There's a question that arose during my development when using callbacks to deliver some event back to the activity/fragment that opened the dialog:
Here's some example code of a simple dialog:
public class DialogTest extends DialogFragment {
public interface DialogTestListener {
public void onDialogPositiveClick(DialogFragment dialog);
}
// Use this instance of the interface to deliver action events
static DialogTestListener mListener;
public static DialogTest newInstance(Activity activity, int titleId, int messageId) {
udateListener(activity);
DialogTest frag = new DialogTest();
Bundle args = new Bundle();
args.putInt("titleId", titleId);
args.putInt("messageId", messageId);
frag.setArguments(args);
return frag;
}
public static void udateListener(Activity activity) {
try {
// Instantiate the NoticeDialogListener so we can send events with it
mListener = (DialogTestListener) activity;
} catch (ClassCastException e) {
// The activity doesn't implement the interface, throw exception
throw new ClassCastException(activity.toString() + " must implement DialogTestListener");
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
int titleId = getArguments().getInt("titleId");
int messageId = getArguments().getInt("messageId");
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// dialog title
builder.setTitle(titleId);
// dialog message
builder.setMessage(messageId);
// dialog negative button
builder.setNegativeButton("No", new OnClickListener() {
public void onClick(DialogInterface dialog, int id) {}});
// dialog positive button
builder.setPositiveButton("Yes", new OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
mListener.onDialogPositiveClick(DialogTest.this);
}});
// create the Dialog object and return it
return builder.create();
}}
And here's some activity code calling it:
public class SomeActivity extends FragmentActivity implements DialogTestListener {
private EditText mUserName;
@Override
public void onCreate(Bundle savedInstanceState) {
// setup ui
super.onCreate(savedInstanceState);
setContentView(R.layout.ui_user_edit);
// name input
mUserName = (EditText) findViewById(R.id.userEdit_editTextName);
}
@Override
public void onDialogPositiveClick(DialogFragment dialog) {
Log.d(TAG, this.toString());
mUserName.setText(mUserName.getText() + "1");
}
private void showDialog() {
DialogTest test = DialogTest.newInstance(SomeActivity.this, R.string.someTitleText, R.string.someMessageText);
test.show(getSupportFragmentManager(), "testDialog");
}}
The code is pretty much what you see the reference. Problem is, that once you do a orientation change, when a dialog is shown, it stops working as expected --> Due to the activity lifecycle, both, the activity and the dialog are rebuild, and the dialog now does not have the proper reference to the new rebuilt activity.
I added the following code to my activitys onResume method:
@Override
protected void onResume() {
super.onResume();
DialogTest.udateListener(this);
}
Doing this, I get the expected behavior, and the dialog sends events back to the new rebuilt activity when an orientation change occured.
My question is: What is the 'best practice' to handle the callbacks between the DialogFragment which was opened by a FragmentActivity during an orientation change?
Best regards
First, call
setTargetFragment
fromFragmentParent
to startdialogFragment
. IndialogFragment
usegetTargetFragment
to callback fragment and return data. All data result will excute inonactivityresult
ofFragmentParent
follow this link: Receive result from DialogFragment
While André's solution works, a better solution is to get the updated activity during
onAttach()
in yourFragment
.With this solution, you won't need to pass the
Activity
innewInstance()
anymore. You just need to make sure theActivity
owning yourFragment
is aDialogTestListener
. You also don't need to save the state like in theResultReceiver
solution.Yeah, this is a common trap I'm falling in all the time myself. First of all let me say that your solution of calling
DialogTest.udateListener()
inonResume()
seems to be fully appropriate to me.An alternative way would be to use a
ResultReceiver
which can be serialized as aParcelable
:Then you can handle everything in the Receiver like this:
Check out ResultReceiver doesn't survire to screen rotation for more details. So in the end you probably still need to rewire the
ResultReceiver
with yourActivity
. The only difference is that you decouple theActivity
from theDialogFragment
.Another way is that you can stop the activity getting recreated. You have to tell Android that you'll handle the orientation change yourself and android won't recreate your activity. You need to add this for your activity to your manifest file:
If not this, then you can use standard
onSaveInstanceState()
to save your state and recover usingsavedInstanceState
as recommended by Google.Here's Google's official guide for it: http://developer.android.com/guide/components/activities.html#Lifecycle
Go through it if you haven't already. It'll really help you in android development.
There is better solution instead of using static methods and variables because it would work only fro one instance of your dialog. It is better to store your callback as non static member
Then you should show your dialog using TAG like this
mDialogFragment.show(getSupportFragmentManager(), DIALOG_TAG);
And then in
onResume
method of your activity you can reset your listener