life - dialogfragment show android
Android DialogFragment vs Dialog (6)
Google recomienda que usemos DialogFragment
lugar de un simple Dialog
usando Fragments API
, pero es absurdo usar un DialogFragment
aislado para un simple cuadro de mensaje de confirmación Sí-No. ¿Cuál es la mejor práctica en este caso?
Generic AlertDialogFragment with Builder Pattern
En mi proyecto, ya AlertDialog.Builder
mucho AlertDialog.Builder
antes de descubrir que es problemático. Sin embargo, no quería cambiar tanto código en ninguna parte de mi aplicación. Además, en realidad soy un fan de pasar OnClickListeners
como clases anónimas donde se necesitan (es decir, cuando se usa setPositiveButton()
, setNegativeButton()
etc.) en lugar de tener que implementar miles de métodos de devolución de llamada para comunicarse entre un fragmento de diálogo y fragmento de titular, que puede, en mi opinión, conducir a un código muy confuso y complejo. Especialmente, si tiene varios cuadros de diálogo diferentes en un fragmento y luego necesita distinguir en las implementaciones de devolución de llamada entre los cuadros de diálogo que se muestran actualmente.
Por lo tanto, combiné diferentes enfoques para crear una clase de ayuda AlertDialogFragment
genérica que se puede usar exactamente como AlertDialog
:
SOLUCIÓN
( TENGA EN CUENTA que estoy usando expresiones lambda de Java 8 en mi código, por lo que es posible que tenga que cambiar partes del código si aún no está utilizando expresiones lambda ).
/**
* Helper class for dialog fragments to show a {@link AlertDialog}. It can be used almost exactly
* like a {@link AlertDialog.Builder}
* <p />
* Creation Date: 22.03.16
*
* @author felix, http://flx-apps.com/
*/
public class AlertDialogFragment extends DialogFragment {
protected FragmentActivity activity;
protected Bundle args;
protected String tag = AlertDialogFragment.class.getSimpleName();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activity = getActivity();
args = getArguments();
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = setDialogDefaults(new AlertDialog.Builder(getActivity())).create();
if (args.containsKey("gravity")) {
dialog.getWindow().getAttributes().gravity = args.getInt("gravity");
}
dialog.setOnShowListener(d -> {
if (dialog != null && dialog.findViewById((android.R.id.message)) != null) {
((TextView) dialog.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance());
}
});
return dialog;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (args.containsKey("onDismissListener")) {
Parcelable onDismissListener = args.getParcelable("onDismissListener");
if (onDismissListener != null && onDismissListener instanceof ParcelableOnDismissListener) {
((ParcelableOnDismissListener) onDismissListener).onDismiss(this);
}
}
}
/**
* Sets default dialog properties by arguments which were set using {@link #builder(FragmentActivity)}
*/
protected AlertDialog.Builder setDialogDefaults(AlertDialog.Builder builder) {
args = getArguments();
activity = getActivity();
if (args.containsKey("title")) {
builder.setTitle(args.getCharSequence("title"));
}
if (args.containsKey("message")) {
CharSequence message = args.getCharSequence("message");
builder.setMessage(message);
}
if (args.containsKey("viewId")) {
builder.setView(getActivity().getLayoutInflater().inflate(args.getInt("viewId"), null));
}
if (args.containsKey("positiveButtonText")) {
builder.setPositiveButton(args.getCharSequence("positiveButtonText"), (dialog, which) -> {
onButtonClicked("positiveButtonListener", which);
});
}
if (args.containsKey("negativeButtonText")) {
builder.setNegativeButton(args.getCharSequence("negativeButtonText"), (dialog, which) -> {
onButtonClicked("negativeButtonListener", which);
});
}
if (args.containsKey("neutralButtonText")) {
builder.setNeutralButton(args.getCharSequence("neutralButtonText"), (dialog, which) -> {
onButtonClicked("neutralButtonListener", which);
});
}
if (args.containsKey("items")) {
builder.setItems(args.getStringArray("items"), (dialog, which) -> {
onButtonClicked("itemClickListener", which);
});
}
// @formatter:off
// FIXME this a pretty hacky workaround: we don''t want to show the dialog if onClickListener of one of the dialog''s button click listener were lost
// the problem is, that there is no (known) solution for parceling a OnClickListener in the long term (only for state changes like orientation change,
// but not if the Activity was completely lost)
if (
(args.getParcelable("positiveButtonListener") != null && !(args.getParcelable("positiveButtonListener") instanceof ParcelableOnClickListener)) ||
(args.getParcelable("negativeButtonListener") != null && !(args.getParcelable("negativeButtonListener") instanceof ParcelableOnClickListener)) ||
(args.getParcelable("neutralButtonListener") != null && !(args.getParcelable("neutralButtonListener") instanceof ParcelableOnClickListener)) ||
(args.getParcelable("itemClickListener") != null && !(args.getParcelable("itemClickListener") instanceof ParcelableOnClickListener))
) {
new DebugMessage("Forgot onClickListener. Needs to be dismissed.")
.logLevel(DebugMessage.LogLevel.VERBOSE)
.show();
try {
dismissAllowingStateLoss();
} catch (NullPointerException | IllegalStateException ignored) {}
}
// @formatter:on
return builder;
}
public interface OnDismissListener {
void onDismiss(AlertDialogFragment dialogFragment);
}
public interface OnClickListener {
void onClick(AlertDialogFragment dialogFragment, int which);
}
protected void onButtonClicked(String buttonKey, int which) {
ParcelableOnClickListener parcelableOnClickListener = getArguments().getParcelable(buttonKey);
if (parcelableOnClickListener != null) {
parcelableOnClickListener.onClick(this, which);
}
}
// region Convenience Builder Pattern class almost similar to AlertDialog.Builder
// =============================================================================================
public AlertDialogFragment builder(FragmentActivity activity) {
this.activity = activity;
this.args = new Bundle();
return this;
}
public AlertDialogFragment addArguments(Bundle bundle) {
args.putAll(bundle);
return this;
}
public AlertDialogFragment setTitle(int titleStringId) {
return setTitle(activity.getString(titleStringId));
}
public AlertDialogFragment setTitle(CharSequence title) {
args.putCharSequence("title", title);
return this;
}
public AlertDialogFragment setMessage(int messageStringId) {
return setMessage(activity.getString(messageStringId));
}
public AlertDialogFragment setMessage(CharSequence message) {
args.putCharSequence("message", message);
return this;
}
public AlertDialogFragment setPositiveButton(int textStringId, OnClickListener onClickListener) {
return setPositiveButton(activity.getString(textStringId), onClickListener);
}
public AlertDialogFragment setPositiveButton(CharSequence text, AlertDialogFragment.OnClickListener onClickListener) {
args.putCharSequence("positiveButtonText", text);
args.putParcelable("positiveButtonListener", createParcelableOnClickListener(onClickListener));
return this;
}
public AlertDialogFragment setNegativeButton(int textStringId, AlertDialogFragment.OnClickListener onClickListener) {
return setNegativeButton(activity.getString(textStringId), onClickListener);
}
public AlertDialogFragment setNegativeButton(CharSequence text, AlertDialogFragment.OnClickListener onClickListener) {
args.putCharSequence("negativeButtonText", text);
args.putParcelable("negativeButtonListener", createParcelableOnClickListener(onClickListener));
return this;
}
public AlertDialogFragment setNeutralButton(int textStringId, AlertDialogFragment.OnClickListener onClickListener) {
return setNeutralButton(activity.getString(textStringId), onClickListener);
}
public AlertDialogFragment setNeutralButton(CharSequence text, AlertDialogFragment.OnClickListener onClickListener) {
args.putCharSequence("neutralButtonText", text);
args.putParcelable("neutralButtonListener", createParcelableOnClickListener(onClickListener));
return this;
}
public AlertDialogFragment setOnDismissListener(OnDismissListener onDismissListener) {
if (onDismissListener == null) {
return this;
}
Parcelable p = new ParcelableOnDismissListener() {
@Override
public void onDismiss(AlertDialogFragment dialogFragment) {
onDismissListener.onDismiss(dialogFragment);
}
};
args.putParcelable("onDismissListener", p);
return this;
}
public AlertDialogFragment setItems(String[] items, AlertDialogFragment.OnClickListener onClickListener) {
args.putStringArray("items", items);
args.putParcelable("itemClickListener", createParcelableOnClickListener(onClickListener));
return this;
}
public AlertDialogFragment setView(int viewId) {
args.putInt("viewId", viewId);
return this;
}
public AlertDialogFragment setGravity(int gravity) {
args.putInt("gravity", gravity);
return this;
}
public AlertDialogFragment setTag(String tag) {
this.tag = tag;
return this;
}
public AlertDialogFragment create() {
setArguments(args);
return AlertDialogFragment.this;
}
public AlertDialogFragment show() {
create();
try {
super.show(activity.getSupportFragmentManager(), tag);
}
catch (IllegalStateException e1) {
/**
* this whole part is used in order to attempt to show the dialog if an
* {@link IllegalStateException} was thrown (it''s kinda comparable to
* {@link FragmentTransaction#commitAllowingStateLoss()}
* So you can remove all those dirty hacks if you are sure that you are always
* properly showing dialogs in the right moments
*/
new DebugMessage("got IllegalStateException attempting to show dialog. trying to hack around.")
.logLevel(DebugMessage.LogLevel.WARN)
.exception(e1)
.show();
try {
Field mShownByMe = DialogFragment.class.getDeclaredField("mShownByMe");
mShownByMe.setAccessible(true);
mShownByMe.set(this, true);
Field mDismissed = DialogFragment.class.getDeclaredField("mDismissed");
mDismissed.setAccessible(true);
mDismissed.set(this, false);
}
catch (Exception e2) {
new DebugMessage("error while showing dialog")
.exception(e2)
.logLevel(DebugMessage.LogLevel.ERROR)
.show();
}
FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
transaction.add(this, tag);
transaction.commitAllowingStateLoss(); // FIXME hacky and unpredictable workaround
}
return AlertDialogFragment.this;
}
@Override
public int show(FragmentTransaction transaction, String tag) {
throw new NoSuchMethodError("Please use AlertDialogFragment.show()!");
}
@Override
public void show(FragmentManager manager, String tag) {
throw new NoSuchMethodError("Please use AlertDialogFragment.show()!");
}
protected ParcelableOnClickListener createParcelableOnClickListener(AlertDialogFragment.OnClickListener onClickListener) {
if (onClickListener == null) {
return null;
}
return new ParcelableOnClickListener() {
@Override
public void onClick(AlertDialogFragment dialogFragment, int which) {
onClickListener.onClick(dialogFragment, which);
}
};
}
/**
* Parcelable OnClickListener (can be remembered on screen rotation)
*/
public abstract static class ParcelableOnClickListener extends ResultReceiver implements AlertDialogFragment.OnClickListener {
public static final Creator<ResultReceiver> CREATOR = ResultReceiver.CREATOR;
ParcelableOnClickListener() {
super(null);
}
@Override
public abstract void onClick(AlertDialogFragment dialogFragment, int which);
}
/**
* Parcelable OnDismissListener (can be remembered on screen rotation)
*/
public abstract static class ParcelableOnDismissListener extends ResultReceiver implements AlertDialogFragment.OnDismissListener {
public static final Creator<ResultReceiver> CREATOR = ResultReceiver.CREATOR;
ParcelableOnDismissListener() {
super(null);
}
@Override
public abstract void onDismiss(AlertDialogFragment dialogFragment);
}
// =============================================================================================
// endregion
}
USO
// showing a normal alert dialog with state loss on configuration changes (like device rotation)
new AlertDialog.Builder(getActivity())
.setTitle("Are you sure? (1)")
.setMessage("Do you really want to do this?")
.setPositiveButton("Yes", (dialog, which) -> Toast.makeText(getContext(), "Yes clicked", Toast.LENGTH_SHORT).show())
.setNegativeButton("Cancel", null)
.show();
// showing a dialog fragment using the helper class with no state loss on configuration changes
new AlertDialogFragment.builder(getActivity())
.setTitle("Are you sure? (2)")
.setMessage("Do you really want to do this?")
.setPositiveButton("Yes", (dialog, which) -> Toast.makeText(getContext(), "Yes clicked", Toast.LENGTH_SHORT).show())
.setNegativeButton("Cancel", null)
.show();
Estoy publicando esto aquí no solo para compartir mi solución, sino también porque quería preguntarles a ustedes, su opinión: ¿Es este enfoque legítimo o problemático hasta cierto punto?
Utilice DialogFragment sobre AlertDialog:
Desde la introducción del nivel 13 de API :
showDialog método showDialog de Activity está en desuso . No es recomendable invocar un diálogo en otro lugar en el código, ya que tendrá que administrar el diálogo usted mismo (por ejemplo, cambio de orientación).
Diferencia DialogFragment - AlertDialog
¿Son tan diferentes? De la referencia de Android con respecto a DialogFragment :
Un DialogFragment es un fragmento que muestra una ventana de diálogo, flotando en la parte superior de la ventana de su actividad. Este fragmento contiene un objeto de diálogo, que se muestra según corresponda en función del estado del fragmento. El control del cuadro de diálogo (decidir cuándo mostrarlo, ocultarlo o descartarlo) debe realizarse a través de la API, no con llamadas directas en el cuadro de diálogo.
Otras notas
- Los fragmentos son una evolución natural en el marco de Android debido a la diversidad de dispositivos con diferentes tamaños de pantalla.
- DialogFragments y Fragments están disponibles en la biblioteca de soporte, lo que hace que la clase se pueda usar en todas las versiones actuales de Android.
Puede crear subclases genéricas de DialogFragment como YesNoDialog y OkDialog, y pasar el título y el mensaje si usa mucho los cuadros de diálogo en su aplicación.
public class YesNoDialog extends DialogFragment
{
public static final String ARG_TITLE = "YesNoDialog.Title";
public static final String ARG_MESSAGE = "YesNoDialog.Message";
public YesNoDialog()
{
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
Bundle args = getArguments();
String title = args.getString(ARG_TITLE);
String message = args.getString(ARG_MESSAGE);
return new AlertDialog.Builder(getActivity())
.setTitle(title)
.setMessage(message)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, null);
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_CANCELED, null);
}
})
.create();
}
}
Entonces llámalo usando lo siguiente:
DialogFragment dialog = new YesNoDialog();
Bundle args = new Bundle();
args.putString(YesNoDialog.ARG_TITLE, title);
args.putString(YesNoDialog.ARG_MESSAGE, message);
dialog.setArguments(args);
dialog.setTargetFragment(this, YES_NO_CALL);
dialog.show(getFragmentManager(), "tag");
Y manejar el resultado en onActivityResult
.
Puedo sugerir una pequeña simplificación de la respuesta de @ ashishduh:
public class AlertDialogFragment extends DialogFragment {
public static final String ARG_TITLE = "AlertDialog.Title";
public static final String ARG_MESSAGE = "AlertDialog.Message";
public static void showAlert(String title, String message, Fragment targetFragment) {
DialogFragment dialog = new AlertDialogFragment();
Bundle args = new Bundle();
args.putString(ARG_TITLE, title);
args.putString(ARG_MESSAGE, message);
dialog.setArguments(args);
dialog.setTargetFragment(targetFragment, 0);
dialog.show(targetFragment.getFragmentManager(), "tag");
}
public AlertDialogFragment() {}
@NonNull
@Override
public AlertDialog onCreateDialog(Bundle savedInstanceState)
{
Bundle args = getArguments();
String title = args.getString(ARG_TITLE, "");
String message = args.getString(ARG_MESSAGE, "");
return new AlertDialog.Builder(getActivity())
.setTitle(title)
.setMessage(message)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, null);
}
})
.create();
}
Elimina la necesidad de que el usuario (de la clase) esté familiarizado con las partes internas del componente y hace que el uso sea realmente simple:
AlertDialogFragment.showAlert(title, message, this);
PD: En mi caso, necesitaba un simple diálogo de alerta, así que eso es lo que creé. Puede aplicar el enfoque a un Sí / No o cualquier otro tipo que necesite.
Sí, use DialogFragment
y en onCreateDialog
puede usar un generador de AlertDialog de todos modos para crear un simple AlertDialog
con botones de confirmación Sí / No. No hay mucho código en absoluto.
Con respecto al manejo de eventos en su fragmento, habría varias formas de hacerlo, pero simplemente defino un DialogFragment
mensajes en mi Fragment
, lo paso al DialogFragment
través de su constructor y luego le DialogFragment
mensajes al controlador de mi fragmento como apropiado para los diversos eventos de clic . De nuevo, varias formas de hacerlo, pero lo siguiente funciona para mí.
En el diálogo mantenga un mensaje y ejemplícelo en el constructor:
private Message okMessage;
...
okMessage = handler.obtainMessage(MY_MSG_WHAT, MY_MSG_OK);
Implemente onClickListener
en su cuadro de diálogo y luego llame al controlador según corresponda:
public void onClick(.....
if (which == DialogInterface.BUTTON_POSITIVE) {
final Message toSend = Message.obtain(okMessage);
toSend.sendToTarget();
}
}
Editar
Y como el Message
es parcelable, puede guardarlo en onSaveInstanceState
y restaurarlo
outState.putParcelable("okMessage", okMessage);
Luego en onCreate
if (savedInstanceState != null) {
okMessage = savedInstanceState.getParcelable("okMessage");
}
Yo recomendaría utilizar DialogFragment
.
Claro, crear un diálogo "Sí / No" es bastante complejo, ya que debería ser una tarea bastante simple, pero crear un cuadro de diálogo similar con Dialog
es sorprendentemente complicado.
(El ciclo de vida de la actividad lo complica. Debe permitir que Activity
administre el ciclo de vida del cuadro de diálogo, y no hay forma de pasar parámetros personalizados, por ejemplo, el mensaje personalizado a Activity.showDialog
si utiliza niveles de API inferiores a 8)
Lo bueno es que generalmente puedes construir tu propia abstracción sobre DialogFragment
bastante facilidad.