I made a custom dialog (extends DialogFragment) that appears in several activities.
If the activity comes to foreground while the dialog is opened, I get the following error:
FATAL EXCEPTION: main
Process: package.name, PID: 11137
java.lang.RuntimeException: Parcelable encountered IOException writing serializable object (name = myFragmentOrActivityWhereOccuredTheError)
...
Caused by: java.io.NotSerializableException: android.support.v7.widget.AppCompatButton
Depends on the activity or fragment, "Caused by" changes, because the problem is not there, is the DialogFragment (without showing the dialog fragments everything works)
There is a solution: calling dismiss() before the activity goes to foreground. But I have to write a lot of code because I have to show it again in case the dialog was opened before the activity came to foreground and also is not simple to handle that with the complexity of the activies.
What I need: Solve the problem without dismissing the dialog. I think I have an error on my DialogFragment Class. So... this is the code of my class:
public class RequestDialog extends DialogFragment {
public static String DIALOG_INTERFACE = "dialogInterface";
public static String REQUIERE_ACTIVACION_MANUAL = "activationMode";
public static String SCHEME = "package";
public interface MyDialogInterface extends Serializable {
void onClickContinuarEvent(int permisoRequerido);
void onClickCancelarEvent(int permisoRequerido);
}
private MyDialogInterface callbackListener;
/**
* dialogInterface - instance of MyDialogInterface which will handle
* callback events
*/
public static RequestDialog getInstance(MyDialogInterface dialogInterface, boolean activationMode) {
RequestDialog fragmentDialog = new RequestDialog();
// set fragment arguments
Bundle args = new Bundle();
args.putSerializable(DIALOG_INTERFACE, dialogInterface);
args.putBoolean(REQUIERE_ACTIVACION_MANUAL, activationMode);
fragmentDialog.setArguments(args);
return fragmentDialog;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle extras = getArguments();
callbackListener = (MyDialogInterface) extras.getSerializable(DIALOG_INTERFACE);
final boolean activationMode = extras.getBoolean(REQUIERE_ACTIVACION_MANUAL);
View view = View.inflate(getActivity(), R.layout.rationale_dialog, null);
TextView texDetalle = view.findViewById(R.id.texDetalle);
TextView texContinuar = view.findViewById(R.id.texContinuar);
TextView texCancelar = view.findViewById(R.id.texCancelar);
ImageView imgCabecera = view.findViewById(R.id.imgCabecera);
imgCabecera.setBackground(ContextCompat.getDrawable(getActivity(), R.drawable.ic_folder));
Typeface typeFace = Typeface.createFromAsset(getActivity().getAssets(), "fonts/MyFont.ttf");
texDetalle.setTypeface(typeFace);
final AlertDialog.Builder requestDialogBuilder = new AlertDialog.Builder(getActivity(), R.style.NarrowDialog);
requestDialogBuilder.setView(view);
final AlertDialog dialog = requestDialogBuilder.create();
dialog.setContentView(view);
final Window window = dialog.getWindow();
if(window != null){
window.setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT);
window.setGravity(Gravity.CENTER);
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
Display display = getActivity().getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
dialog.getWindow().setLayout(size.x*70/100, WindowManager.LayoutParams.WRAP_CONTENT);
texContinuar.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(activationMode){
goToPreferencesSystem();
callbackListener.onClickCancelarEvent(1);
}
else{
callbackListener.onClickContinuarEvent(0);
}
dialog.dismiss();
}
});
texCancelar.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
callbackListener.onClickCancelarEvent(2);
dialog.dismiss();
}
});
}
setCancelable(false);
return dialog;
}
}
As you can see, there is two callback that I have to handle in the activies.
So... Do you have any idea hoy to solve this problem?
Thanks!
EDIT:
In any activity I have to implement the dialog like this:
MyActivity implements RequestDialog.MyDialogInterface
And then override the callbacks:
#Override
public void onClickContinuarEvent(int request) {
}
#Override
public void onClickCancelarEvent(int permisoRequerido) {}
You really should not be making anything Serializable that should die when the lifecycle of a fragment/activity die. For this solution, remove the interface on the getInstance(). You should not pass interfaces via fragment creation. You should create a setter for the interface. I don't have nearly enough information to solve the issue but, I believe this may be the solution. Let me know if it works, so I can delete if it it doesn't.
Dialog
public class RequestDialog extends DialogFragment {
private MyDialogInterface callbackListener;
public interface MyDialogInterface {
void onClickContinuarEvent(int permisoRequerido);
void onClickCancelarEvent(int permisoRequerido);
}
public void setCallbackListener(MyDialogInterface callbackListener) {
this.callbackListener = callbackListener;
}
public static RequestDialog getInstance( boolean activationMode) {
RequestDialog fragmentDialog = new RequestDialog();
Bundle args = new Bundle();
args.putBoolean(REQUIERE_ACTIVACION_MANUAL, activationMode);
fragmentDialog.setArguments(args);
return fragmentDialog;
}
}
Create Dialog
RequestDialog requestDialog = RequestDialog.getInstance(true);
requestDialog.setCallbackListener(new RequestDialog.MyDialogInterface() {
#Override
public void onClickContinuarEvent(int permisoRequerido) {
}
#Override
public void onClickCancelarEvent(int permisoRequerido) {
}
});
requestDialog.show(getSupportFragmentManager(), "REQUEST_DIALOG");
Related
I'm currently having trouble setting up my custom listener. I just want to pass a string from my dialog to my fragment (where I set up the dialog). I was trying to follow this tutorial: https://www.youtube.com/watch?v=ARezg1D9Zd0.
At minute 10:38, he sets up the listener.
This only problem is that in this, he uses DialogFragment, but I'm extending dialog and I don't know how to attach the context to the listener.
I've tried to set it up in onAttachedToWindow() and in the dialog constructor but it crashes.
What should I actually do?
I'd also appreciate it if someone could explain what the difference is between:
onAttachedToWindow() vs. onAttach(Context context).
Thanks!
MY CUSTOM DIALOG BOX:
public class NewListDialog extends Dialog implements View.OnClickListener {
private Activity c;
private TextInputLayout textInputLayout;
private TextInputEditText editText;
private LinearLayout dialog_root_view;
private Animation fade_out;
private String list_name;
private NewListDialogListener listener;
NewListDialog(Activity a) {
super(a);
this.c = a;
//ANOTHER ATTEMPT TO ATTACH CONTEXT TO LISTENER
//listener = (NewListDialogListener) a.getApplicationContext();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.new_list_dialog);
MaterialButton cancel = findViewById(R.id.dialog_new_list_cancel_button);
MaterialButton create = findViewById(R.id.dialog_new_list_create_button);
textInputLayout = findViewById(R.id.dialog_text_input_layout);
editText = findViewById(R.id.dialog_edit_text);
dialog_root_view = findViewById(R.id.dialog_root);
fade_out = AnimationUtils.loadAnimation(c, R.anim.fade_out_dialog);
editText.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View view, int i, KeyEvent keyEvent) {
if (isTextValid(editText.getText())) {
textInputLayout.setError(null);
return true;
}
return false;
}
});
cancel.setOnClickListener(this);
create.setOnClickListener(this);
}
#Override
public void onClick(View view) {
switch (view.getId()) {
//Cancel Button
case R.id.dialog_new_list_cancel_button:
dialog_root_view.startAnimation(fade_out);
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
dismiss();
}
}, 200);
break;
//Create Button
case R.id.dialog_new_list_create_button:
if (!isTextValid(editText.getText())) {
textInputLayout.setError(c.getString(R.string.dialog_error));
} else {
textInputLayout.setError(null);
//record input string
list_name = editText.getText().toString();
//send information to parent activity
//What to put here?
listener.createListName(list_name);
dismiss();
}
break;
default:
break;
}
}
private boolean isTextValid(#Nullable Editable text) {
return text != null && text.length() > 0;
}
//ATTEMPT TO ATTACH CONTEXT TO LISTENER
#Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
try {
listener = (NewListDialogListener) c.getBaseContext();
} catch (ClassCastException e) {
throw new ClassCastException(c.getBaseContext().toString() + "must implement ExampleDialogListener");
}
}
public interface NewListDialogListener {
void createListName(String listname);
}
}
In case you define a custom dialog then you can declare a method to allow other components call it or listen events on this dialog. Add this method to you custom dialog.
public void setNewListDialogListener(NewListDialogListener listener){
this.listener = listener;
}
NewListDialog.java
public class NewListDialog extends Dialog implements View.OnClickListener {
private Activity c;
private TextInputLayout textInputLayout;
private TextInputEditText editText;
private LinearLayout dialog_root_view;
private Animation fade_out;
private String list_name;
private NewListDialogListener listener;
NewListDialog(Activity a) {
super(a);
this.c = a;
}
public void setNewListDialogListener(NewListDialogListener listener) {
this.listener = listener;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.new_list_dialog);
MaterialButton cancel = findViewById(R.id.dialog_new_list_cancel_button);
MaterialButton create = findViewById(R.id.dialog_new_list_create_button);
textInputLayout = findViewById(R.id.dialog_text_input_layout);
editText = findViewById(R.id.dialog_edit_text);
dialog_root_view = findViewById(R.id.dialog_root);
fade_out = AnimationUtils.loadAnimation(c, R.anim.fade_out_dialog);
editText.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View view, int i, KeyEvent keyEvent) {
if (isTextValid(editText.getText())) {
textInputLayout.setError(null);
return true;
}
return false;
}
});
cancel.setOnClickListener(this);
create.setOnClickListener(this);
}
#Override
public void onClick(View view) {
switch (view.getId()) {
//Cancel Button
case R.id.dialog_new_list_cancel_button:
dialog_root_view.startAnimation(fade_out);
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
dismiss();
}
}, 200);
break;
//Create Button
case R.id.dialog_new_list_create_button:
if (!isTextValid(editText.getText())) {
textInputLayout.setError(c.getString(R.string.dialog_error));
} else {
textInputLayout.setError(null);
//record input string
list_name = editText.getText().toString();
//send information to parent activity
//What to put here?
if (listener != null) {
listener.createListName(list_name);
}
dismiss();
}
break;
default:
break;
}
}
private boolean isTextValid(#Nullable Editable text) {
return text != null && text.length() > 0;
}
public interface NewListDialogListener {
void createListName(String listname);
}
}
In other components such as an activity which must implements NewListDialogListener.
NewListDialog dialog = new NewListDialog(this);
dialog.setNewListDialogListener(this);
If you don't want the activity implements NewListDialogListener then you can pass a listener instead.
NewListDialog dialog = new NewListDialog(this);
dialog.setNewListDialogListener(new NewListDialog.NewListDialogListener() {
#Override
public void createListName(String listname) {
// TODO: Your code here
}
});
In android Fragments and Activity has lifecycles. Fragments are hosted inside Activity and get the context of host activity via onattach method.
On the other hand Dialog is extended from Object (God class) without any lifecycle and should be treaded as an object.
If your activity is implementing NewListDialogListener then you can do
listener = (NewListDialogListener) a;
onAttachedToWindow : mean the dialog will be drawn on screen soon
and
getApplicationContext() will give you the context object of the application (one per app) which is surely not related with your listener and hence won't work
Reference :
Android DialogFragment vs Dialog
Difference between getContext() , getApplicationContext() , getBaseContext() and “this”
You can use RxAndroid instead of using listener, in this situation I use RxAndroid to get data from dialogs to activities or fragments.
Just need to create a PublishSubject and get the observed data. on activity or fragment :
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PublishSubject<String > objectPublishSubject = PublishSubject.create();
objectPublishSubject.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.subscribe(this::onNext);
CustomDialog customDialog = new CustomDialog(this, objectPublishSubject);
customDialog.show();
}
private void onNext(String data) {
Log.i("DIALOG_DATA", data);
}
and you can create dialog like this :
public class CustomDialog extends Dialog implements View.OnClickListener {
private PublishSubject<String> subject;
public CustomDialog(#NonNull Context context, PublishSubject<String> subject) {
super(context);
this.subject = subject;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.custom_dialog);
findViewById(R.id.button).setOnClickListener(this);
}
#Override
public void onClick(View v) {
subject.onNext("Data");
dismiss();
}
Hi there I'm thinking about what is the correct and best way to handle Activity, Fragment, AsyncTask and DialogFragments together.
My current state is that I start my Activity and replace its ContentView with my Fragment, in which I got an EditText and one Button.
Tapping my Button executes an AsyncTasks which Requests random things and takes some time. Meanwhile I display a DialogFragment begging for patience.
Desired behavior is that, e.g. I rotate my screen my DialogFragment keeps being displayed for the time my AsyncTask is running. After that I want to show up a simple toast displaying the information I got from my HttpRequest.
Compact overview about how I thought it would work:
BaseFragment keeps a WeakReference to the Activity it's attached to
AsyncTask keeps a WeakReference to Fragment which exectures it
AsyncTasks onPreExecute() shows up the DialogFragment
AsyncTasks onPostExecute() dissmisses the DialogFragment
BaseFragment holds DialogFragment
Unfortunately this is not the way it works, on orientation change my DialogFragment keeps being displayed and no toast is showing up.
What am I doing wrong ?
public class BaseFragment extends Fragment{
private static final String TAG = BaseFragment.class.getSimpleName();
protected WeakReference<AppCompatActivity> mActivity;
private TemplateDialogFragment dialogFragment;
public WeakReference<AppCompatActivity> getAppCompatActivity(){ return mActivity; }
#Override
public void onAttach(Context context) {
if(!(context instanceof AppCompatActivity)) {
throw new IllegalStateException(TAG + " is not attached to an AppCompatActivity.");
}
mActivity = new WeakReference<>((AppCompatActivity) context);
super.onAttach(context);
}
#Override
public void onDetach() {
mActivity = null;
super.onDetach();
}
#Override
public void onStart() {
super.onStart();
showContent();
}
public void showContent(){
}
public void showDialog(String title, String content){
dialogFragment = new TemplateDialogFragment();
Bundle bundle = new Bundle();
bundle.putString(TemplateDialogFragment.DIALOG_TITLE, title);
bundle.putString(TemplateDialogFragment.DIALOG_MESSAGE, content);
dialogFragment.setArguments(bundle);
dialogFragment.show(getFragmentManager(), TemplateDialogFragment.FRAGMENT_TAG);
}
public void notifyTaskFinished(String result) {
dismissDialog();
if(mActivity != null && !mActivity.get().isFinishing()) {
Toast.makeText(mActivity.get().getApplicationContext(), result, Toast.LENGTH_LONG).show();
}
}
private void dismissDialog(){
if(dialogFragment != null && dialogFragment.isAdded()) {
dialogFragment.dismissAllowingStateLoss();
}
}
}
...
public class TemplateFragment extends BaseFragment {
private static final String TAG = TemplateFragment.class.getSimpleName();
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.test_fragment, container, false);
}
#Override
public void showContent() {
super.showContent();
Button startTask = (Button) getAppCompatActivity().get().findViewById(R.id.button0);
final BaseFragment instance = this;
startTask.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
CustomAsyncTask task = new CustomAsyncTask(instance);
EditText input = (EditText) getAppCompatActivity().get().findViewById(R.id.text0);
task.execute(input.getText().toString());
}
});
}
private static class CustomAsyncTask extends AsyncTask<String, Void, String> {
WeakReference<BaseFragment> weakBaseFragmentReference;
private CustomAsyncTask(BaseFragment fragment) {
weakBaseFragmentReference = new WeakReference<>(fragment);
}
#Override
protected void onPreExecute() {
super.onPreExecute();
weakBaseFragmentReference.get().showDialog("Executing", "Working on the request...");
}
#Override
protected String doInBackground(String... params) {
HttpURLConnection con = HttpUrlConnectionFactory.createUrlConnection("https://www.httpbin.org/bytes/" + (params[0] == null ? "1" : params[0]));
return HttpRequester.doGet(con).getResponseAsString();
}
#Override
protected void onPostExecute(String response) {
super.onPostExecute(response);
if(weakBaseFragmentReference.get() == null) {
return;
}
weakBaseFragmentReference.get().notifyTaskFinished(response);
}
}
}
*Edit:
After some time researching this theme I'm sure a Service is the best solution for most of my field of use. Also I used AsyncTaskLoaders a lot, because there is a smooth control of lifecycle....
Use progress bar instead of DialogFragment.
AsyncTask should only be used for tasks that take quite few seconds.
AsyncTask doesn't respect Activity lifecycle, and can lead to memory leaks.
Check some gotchas.
You can try AsyncTaskLoader to survive configuration changes.
I've an AppCompatActivity that uses the NavigationDrawer pattern, managing some fragments. In one of these, that has no setRetainInstance(true), I show a DialogFragment with a ProgressDialog inside and an AsyncTask with this code:
SavingLoader savingLoader = SavingLoader.newInstance(savingLoaderMaxValue);
savingLoader.show(getChildFragmentManager(), SAVING_LOADER_TAG);
new MyAsyncTask().execute();
Where the SavingLoader class is this one:
public class SavingLoader extends DialogFragment {
private static final String MAX_VALUE_TAG = "MAX_VALUE_TAG";
private static final String PROGRESS_VALUE_TAG = "PROGRESS_VALUE_TAG";
public static SavingLoader newInstance(int max_value){
SavingLoader s = new SavingLoader();
Bundle args = new Bundle();
args.putInt(MAX_VALUE_TAG, max_value);
s.setArguments(args);
return s;
}
private ProgressDialog dialog;
public SavingLoader(){}
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setCancelable(false);
}
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState){
dialog = new ProgressDialog(getActivity(), getTheme());
dialog.setTitle(getString(R.string.dialog_title_saving));
dialog.setMessage(getString(R.string.dialog_message_saving));
dialog.setIndeterminate(false);
int max = (savedInstanceState == null ?
getArguments().getInt(MAX_VALUE_TAG) : savedInstanceState.getInt(MAX_VALUE_TAG));
if (max >= 1){
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setProgress((savedInstanceState == null ?
0 : savedInstanceState.getInt(PROGRESS_VALUE_TAG)));
dialog.setMax(max);
} else dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
return dialog;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(MAX_VALUE_TAG, dialog.getMax());
outState.putInt(PROGRESS_VALUE_TAG, dialog.getProgress());
}
public int getProgress(){
return dialog.getProgress();
}
public int getMax(){
return dialog.getMax();
}
public void incrementProgressBy(int value){
if (dialog.getProgress() + value <= dialog.getMax())
dialog.incrementProgressBy(value);
}
}
In the onPostExecute() method I need to perform some UI update so here's my problem: if I start the dialog and the AsyncTask (like above) and I don't rotate my phone, all works as expected. Same thing if I rotate phone AFTER the onPostExecute() method. But if I rotate my phone WHILE the AsyncTask is still running, when it completes and reach the onPostExecute() method it gives me the IllegalStateException saying that the fragment hosting the AsyncTask and the Dialogfragment is no longer attached to the activity. So I tried to override both the onAttach() and the onDetach() methods (with a simple System.out.println) of my fragment, to see when the onPostExecute() gets called. The result is that when I rotate my phone, I always got this output:
onDetach
onAttach
... (if I rotate more my phone)
onPostExecute
So shouldn't the fragment be attached when the AsyncTask completes? Thank you all for your time and attention.
I've finally managed to solve this problem by stop using AsyncTask and using LoaderManager + AsyncTaskLoader following this article. In short, your fragment must implement the LoaderCallbacks interface and manage the AsyncTaskLoader. A skeleton fragment could be something like this:
public class MyFragment extends Fragment implements LoaderManager.LoaderCallbacks {
#Override
public View onCreateView(LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
// Inflate here your view as you usually do and find your components
// For example imagine to have a button tha will fire the task
Button b = (Button) view.findViewById(R.id.my_button);
b.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Use this to start task for the first time
getLoaderManager().initLoader(0, null, this);
// .. or this for restart the task, details in
// the provided article
// getLoaderManager().restartLoader(0, null, this);
}
});
// Get fragments load manager
LoaderManager lm = getLoaderManager();
if (lm.getLoader(0) != null) {
// Reconnect to an existing loader
lm.initLoader(0, null, this);
}
// Return your view here
return view;
}
// LoaderCallbacks methods to override
#Override
public Loader onCreateLoader(int id, Bundle args) {
// Create an instance of the loader passing the context
MyTaskLoader loader = new MyTaskLoader(getActivity());
loader.forceLoad();
return loader;
}
#Override
public void onLoadFinished(Loader loader, Object data) {
// Use this callback as you would use the AsyncTask "onPostExecute"
}
#Override
public void onLoaderReset(Loader loader) {}
// Now define the loader class
private static class MyTaskLoader extends AsyncTaskLoader {
public MyTaskLoader(Context context){
super(context);
}
#Override
public Object loadInBackground() {
// Do here your async work
}
}
}
I am using a DialogFragment, which I am showing like this from an Activity:
DialogFragmentImage dialog = DialogFragmentImage.newInstance(createBitmap());
dialog.onDismiss(dialog);.onDismiss(this);
dialog.show(getFragmentManager(), "DialogFragmentImage");
I would like to check when the DialogFragment was dismissed (for example when the back button was pressed), but in my Activity. How can I do that? How can I "tell" my activity that the DialogFragment has been dismissed?
Make your Activity implement OnDismissListener
public final class YourActivity extends Activity implements DialogInterface.OnDismissListener {
#Override
public void onDismiss(final DialogInterface dialog) {
//Fragment dialog had been dismissed
}
}
DialogFragment already implements OnDismissListener, just override the method and call the Activity.
public final class DialogFragmentImage extends DialogFragment {
///blah blah
#Override
public void onDismiss(final DialogInterface dialog) {
super.onDismiss(dialog);
final Activity activity = getActivity();
if (activity instanceof DialogInterface.OnDismissListener) {
((DialogInterface.OnDismissListener) activity).onDismiss(dialog);
}
}
}
If you're starting the dialog from a fragment using the childFragment manager (API>=17), you can use getParentFragment to talk to the onDismissListener on the parent fragment.:
public final class DialogFragmentImage extends DialogFragment {
///blah blah
#Override
public void onDismiss(final DialogInterface dialog) {
super.onDismiss(dialog);
Fragment parentFragment = getParentFragment();
if (parentFragment instanceof DialogInterface.OnDismissListener) {
((DialogInterface.OnDismissListener) parentFragment).onDismiss(dialog);
}
}
}
Here is my answer. It's a bit late but it's maybe benefit someone passing by.
FragmentManager fm = getFragmentManager();
YourDialogFragment dialog = new YourDialogFragment();
dialog.show(fm,"MyDialog");
fm.executePendingTransactions();
dialog.getDialog().setOnDismissListener(new DialogInterface.OnDismissListener() {
#Override
public void onDismiss(DialogInterface dialogInterface) {
//do whatever you want when dialog is dismissed
}
});
We need to call
fm.executePendingTransactions();
To make sure that FragmentTransaction work has been performed. Otherwise NullPointerException can occur when calling setOnDismissListener().
Sorry if there is any mistake. Hope this help.
This is an old issue but I found no solution I am happy with. I don't like passing any Listeners to my DialogFragment or set a TargetFragment, because that may break on orientation change. What do you think about this?
MyDialog d = new MyDialog();
d.show(fragmentManager, "tag");
fragmentManager.registerFragmentLifecycleCallbacks(new FragmentManager.FragmentLifecycleCallbacks() {
#Override
public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) {
super.onFragmentViewDestroyed(fm, f);
//do sth
fragmentManager.unregisterFragmentLifecycleCallbacks(this);
}
}, false);
Alternative answer, if you don't have access to the methode onDismiss of activity.
//DIALOGFRAGMENT
//Create interface in your DialogFragment (or a new file)
public interface OnDismissListener {
void onDismiss(MyDialogFragment myDialogFragment);
}
//create Pointer and setter to it
private OnDismissListener onDismissListener;
public void setDissmissListener(DissmissListener dissmissListener) {
this.dissmissListener = dissmissListener;
}
//Call it on the dialogFragment onDissmiss
#Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (onDismissListener != null) {
onDismissListener.onDismiss(this);
}
}
//OTHER CLASS, start fragment where you want
MyDialogFragment df = new MyDialogFragment();
df.setOnDismissListener(new MyDialogFragment.OnDismissListener() {
#Override
public void onDismiss(MyDialogFragment myDialogFragment) {
//Call when MyDialogFragment close
}
});
df.show(activity.getFragmentManager(), "myDialogFragment");
edit : if system need to recreate DialogFragment:
you can find it with
MyDialogFragment myDialogFragment = getFragmentManager().findFragmentByTag("MyDialogFragment");
if(myDialogFragment != null) {
myDialogFragment.setOnDismissListener(...);
}
public class OpcoesProdutoDialogo extends DialogFragment{
private DialogInterface.OnDismissListener onDismissOuvinte;
.
.
.
#Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if(onDismissOuvinte!=null)
onDismissOuvinte.onDismiss(dialog);
}
public void setOnDismissListener(#Nullable DialogInterface.OnDismissListener listener) {
this.onDismissOuvinte = listener;
}
}
and in call
OpcoesProdutoDialogo opcProduto = OpcoesProdutoDialogo.criar(itemPedido);
opcProduto.show(getFragmentManager(), "opc_produto_editar");
opcProduto.setOnDismissListener(d->{
adapterItens.notifyItemChanged(posicao);
});
You can subclass DialogFragment and provide your own listener that is going to be called and in onCancel.
var onDismissListener: (() -> Unit)? = null
For the ones not familiar with Kotlin this is just an anonymous interface that saves boilerplate iterface in Java. Use a field and a setter in Java.
And then in onCancel
override fun onCancel(dialog: DialogInterface?) {
super.onCancel(dialog)
onDismissListener?.invoke()
}
Have fun!
If you don't like the solution of #yaroslav-mytkalyk, in which the fragment needs to cast the activity / parent fragment, here's another one:
Here's the idea:
Expose a listener in your fragment, DialogFragmentImage.
Implement the listener in your activity and pass it to the fragment when creating it. Make sure to use a tag as well in order to be able to find the fragment later (read below).
In onStop(), remove the listener in order not to leak the activity if it's destroyed. This will happen when the screen is rotated, as the activity will be re-created.
In onResume(), check if the fragment exists and if yes, re-add the listener.
Expose a listener from your fragment:
class MyFragment extends DialogFragment {
public interface OnDismissListener {
void dismissed();
}
#Nullable
private OnDismissListener onDismissListener;
public void setOnDismissListener(#Nullable OnDismissListener onDismissListener) {
this.onDismissListener = onDismissListener;
}
/*
If you are calling dismiss() or dismissAllowingStateLoss() manually,
don't forget to call:
if (onDismissListener != null) {
onDismissListener.dismissed();
}
Otherwise, override them and call it there.
*/
}
And this is how your activity should look like:
class MyActivity extends AppCompatActivity {
private static final String MY_FRAGMENT_TAG = "my_fragment";
private MyFragment.OnDismissListener myFragmentListener = () -> {
// ...
};
/**
* Shows the fragment. Note that:
* 1. We pass a tag to `show()`.
* 2. We set the listener on the fragment.
*/
private void showFragment() {
MyFragment fragment = new MyFragment();
fragment.show(getSupportFragmentManager(), MY_FRAGMENT_TAG);
fragment.setOnDismissListener(myFragmentListener);
}
#Override
protected void onStart() {
super.onStart();
// Restore the listener that we may have removed in `onStop()`.
#Nullable MyFragment myFragment = (MyFragment) getSupportFragmentManager().findFragmentByTag(MY_FRAGMENT_TAG);
if (myFragment != null) {
myFragment.setOnDismissListener(myFragmentListener);
}
}
#Override
protected void onStop() {
// If the fragment is currently shown, remove the listener so that the activity is not leaked when e.g. the screen is rotated and it's re-created.
#Nullable MyFragment myFragment = (MyFragment) getSupportFragmentManager().findFragmentByTag(MY_FRAGMENT_TAG);
if (myFragment != null) {
myFragment.setOnDismissListener(null);
}
super.onStop();
}
}
Care : all example aren't correct because your fragment should have a no-arg constructor !
Working code with back gesture and close button in the fragment itself. I removed useless code stuff like getting arg in onCreate etc.
Important : onDismiss is also call when orientation change so as a result you should check if the context is not null in your callback (or using other stuff).
public class MyDialogFragment extends DialogFragment {
public static String TAG = "MyFragment";
public interface ConfirmDialogCompliant {
void doOkConfirmClick();
}
public MyFragment(){
super();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_layout, container, false);
((ImageButton) rootView.findViewById(R.id.btn_close)).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// close fragment
dismiss();
}
});
return rootView;
}
#Override
public void onDismiss(#NonNull DialogInterface dialog) {
super.onDismiss(dialog);
// notify
if(caller != null)
caller.doOkConfirmClick();
}
}
public void setCallback(ConfirmDialogCompliant caller) {
this.caller = caller;
}
public static MyDialogFragment newInstance(String id) {
MyDialogFragment f = new MyDialogFragment();
// Supply num input as an argument.
Bundle args = new Bundle();
args.putString("YOU_KEY", id);
f.setArguments(args);
return f;
}
}
And now how to call it from parent.
MyDialogFragment.ConfirmDialogCompliant callback = new MyDialogFragment.ConfirmDialogCompliant() {
#Override
public void doOkConfirmClick() {
// context can be null, avoid NPE
if(getContext() != null){
}
}
};
MyDialogFragment fragment = MyDialogFragment.newInstance("item");
fragment.setCallback(callback);
fragment.show(ft, MyDialogFragment.TAG);
new MyDialogFragment(callback, item);
fragment.show(getActivity().getSupportFragmentManager(), MyDialogFragment.TAG);
Additionnal source : https://developer.android.com/reference/android/app/DialogFragment
Kotlin Answer
private fun showMyCustomDialog() {
// Show.
MyCustomDialogFragment().show(fm, "MyCustomDialogFragment")
// Set pending transactions.
fm.executePendingTransactions()
// Listen dialog closing.
MyCustomDialogFragment().dialog?.setOnDismissListener {
// You can do you job when it closed.
}
}
Solution using kotlin and additional interface. (an example for a fragment will be shown here, but with a few changes it will work in an activity as well)
First you need to create an interface (the set of parameters can be any):
interface DialogCloseListener {
fun handleDialogClose(dialog: DialogInterface)
}
Then implement this interface in the fragment that calls the DailogFragment:
class YourParentFragment: Fragment(), DialogCloseListener {
override fun handleDialogClose(dialog: DialogInterface) {
// do something
}
}
Now go to your DialogFragment. Implement the onDismiss method. In it, check if the parent fragment implements your interface, call your method, passing the necessary parameters there:
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
if(parentFragment is DialogCloseListener){
(parentFragment as DialogCloseListener).handleDialogClose(dialog)
}
}
I think that this way is good because you can track a specific close event (by passing a certain parameter to the method), for example, canceling an order, and somehow handle it.
Try this
dialog.setOnDismissListener {
Log.e("example","example")
}
Have Fun!
I try to implement a callback from a DialogFragment.
There is a good example, but they don't open this DialogFragment from a Fragment.
http://developer.android.com/guide/topics/ui/dialogs.html#PassingEvents
So here is my code:
public class EditDateDialogFragment extends DialogFragment {
// Use this instance of the interface to deliver action events
EditDateDialogListener mListener;
/* The activity that creates an instance of this dialog fragment must
* implement this interface in order to receive event callbacks.
* Each method passes the DialogFragment in case the host needs to query it. */
public interface EditDateDialogListener {
public void onDialogPositiveClick(DialogFragment dialog);
public void onDialogNegativeClick(DialogFragment dialog);
}
public static EditDateDialogFragment newInstance( int currentCategoryId ) {
EditDateDialogFragment p = new EditDateDialogFragment();
Bundle args = new Bundle();
args.putInt("currentRecordId", currentCategoryId);
p.setArguments(args);
return p;
}
#Override
public void onCreate(Bundle savedInstanceState) {
mCurrentRecordId = getArguments().getInt("currentRecordId");
super.onCreate(savedInstanceState);
}
public void onAttach(SherlockActivity activity) {
super.onAttach(activity);
try {
// Instantiate the EditDateDialogListener so we can send events to the host
mListener = (EditDateDialogListener) activity;
} catch (ClassCastException e) {
// The activity doesn't implement the interface, throw exception
throw new ClassCastException(activity.toString() + " must implement EditDateDialogListener");
}
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
LayoutInflater inflater = LayoutInflater.from(getActivity());
final View v = inflater.inflate(R.layout.fragment_dialog_edit_date, null);
return new AlertDialog.Builder(getActivity()).setTitle("Set Date...").setView(v).setCancelable(true).setPositiveButton("Confirm", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
Log.d("", "Dialog confirmed");
mListener.onDialogPositiveClick(EditDateDialogFragment.this);
}
}).setNegativeButton("Abort", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
Log.d("", "Dialog abort");
dialog.cancel();
}
}).create();
}
}
In RecordDetailFragment.java i implement the interface and create a new instance of the EditDateDialogFragment at this way (just the important parts):
public class RecordDetailFragment extends SherlockFragment implements EditDateDialogFragment.EditDateDialogListener {
...
DialogFragment editDateFragment = EditDateDialogFragment.newInstance( recordId );
editDateFragment.show(getActivity().getSupportFragmentManager(), "EditDateDialogFrame");
#Override
public void onDialogPositiveClick(DialogFragment dialog) {
LOGD(TAG, "Overriden Dialog confirmed");
//((EditDateDialogFragment) dialog).mDatePicker;
}
#Override
public void onDialogNegativeClick(DialogFragment dialog) {
// TODO Auto-generated method stub
}
...
}
Now the public void onAttach(SherlockActivity activity) in the EditDateDialogFragment is never called, because I create the DialogFragment from a Fragment instead of an Activity?
How to fix this?
UPDATE:
In the RecordDetailFragment I insert this into the onCreate()
if (savedInstanceState != null) {
EditDateDialogFragment dpf = (EditDateDialogFragment) getActivity().getSupportFragmentManager().findFragmentByTag("EditDateDialogFragment");
if (dpf != null) {
dpf.setListener((EditDateDialogListener) this);
}
}
I changed the instantiation of the DialogFragment to
EditDateDialogFragment editDateFragment = EditDateDialogFragment.newInstance( recordId );
editDateFragment.setListener((EditDateDialogListener) this);
editDateFragment.show(getActivity().getSupportFragmentManager(), "EditDateDialogFragment");
Note the EditDateDialogFragment instead of DialogFragment.
I'm not sure how to update the reference in the dialog.
Just jumped into the same problem, the solution was very simple. Instead of overriding
public void onAttach(Context context) {}
override this:
public void onAttach(Activity activity) {}
Everything is now fine with DialogFragment.
How to fix this?
I'm guessing that you want the RecordDetailFragment instance to behave as the EditDateDialogListener for the DialogFragment. If yes then you need to explicitly set it(and update it) as the listener:
DialogFragment editDateFragment = EditDateDialogFragment.newInstance( recordId );
editDataFragment.setListener(RecordDetailFragment.this);
editDateFragment.show(getActivity().getSupportFragmentManager(), "EditDateDialogFrame");
Where setListener() is a method in the EditDialogFragment like this:
public void setListener(EditDateDialogListener listener) {
mListener = listener;
}
As the user rotates the phone, for example, the Activity along with its fragments will be recreated and you need to re set the listener to point to the newly created RecordDetailFragment instance(you may want to use a WeakReference for mListener). Something similar you can find in this answer(you'll look for the two fragments in the onCreate).
Edit: In the onCreate method of the Activity:
if (savedInstanceState != null) {
RecordDetailFragment df = (RecordDetailFragment) getSupportFragmentManager().findFragmentByTag("rdf"); // "rdf" is the tag used when you add the RecordDetailFragment to the activity
EditDateDialogFragment s = (EditDateDialogFragment) getSupportFragmentManager().findFragmentByTag("tag"); // "tag" is the string set as the tag for the dialog when you show it
if (s != null) {
// the dialog exists so update its listener
s.setListener(df);
}
}
Somewhere in the onCreateDialog cast the mListener to the getActivity():
try {
mListener = (EditDateDialogListener) getActivity();
} catch (Exception e) {
throw new ClassCastException(getActivity().toString()
+ " must implement EditDateDialogListener");
}
A more "modern" approach is to use the new Fragment Result API.
Add a result listener on Fragment A (parent) onCreate:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
childFragmentManager.setFragmentResultListener("requestKey", this) { key, bundle ->
val result = bundle.getString("bundleKey")
}
}
Wherever you need, set result on child Fragment B (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#kotlin