Recibir resultado de DialogFragment

232

Estoy usando DialogFragments para varias cosas: elegir un elemento de la lista, ingresar texto.

¿Cuál es la mejor manera de devolver un valor (es decir, una cadena o un elemento de una lista) a la actividad / fragmento de llamada?

Actualmente estoy implementando la actividad de llamadas DismissListenery le estoy dando al DialogFragment una referencia a la actividad. Luego, OnDimissel cuadro de diálogo llama al método en la actividad y la actividad toma el resultado del objeto DialogFragment. Muy desordenado y no funciona en el cambio de configuración (cambio de orientación) ya que DialogFragment pierde la referencia a la actividad.

Gracias por cualquier ayuda.

James Cross
fuente
10
Los DialogFragments siguen siendo solo fragmentos. Su enfoque es en realidad la forma recomendada para que los fragmentos utilicen para responder a la actividad principal. developer.android.com/guide/topics/fundamentals/…
codinguser
1
Gracias por eso. Estaba muy cerca (como dijiste). La parte con la que me ayudó ese documento vinculado fue usar onAttach () y transmitir la actividad a un oyente.
James Cross
2
@codinguser, @Styx - "dando al DialogFragment una referencia a la actividad" - este detalle es un poco arriesgado, ya que tanto el Activitycomo el DialogFragmentpodrían ser recreados. Usar el Activitypaso a onAttach(Activity activity)es la forma correcta y recomendada.
sstn

Respuestas:

246

Úselo myDialogFragment.setTargetFragment(this, MY_REQUEST_CODE)desde el lugar donde muestra el cuadro de diálogo, y luego cuando termine su cuadro de diálogo, desde allí puede llamar getTargetFragment().onActivityResult(getTargetRequestCode(), ...)e implementar onActivityResult()en el fragmento que lo contiene.

Parece un abuso de onActivityResult(), especialmente porque no involucra actividades en absoluto. Pero lo he visto recomendado por la gente oficial de Google, y tal vez incluso en las demostraciones de la API. Creo que es para lo que g/setTargetFragment()se agregaron.

Timmmm
fuente
2
setTargetFragment menciona que el código de solicitud es para usar en onActivityResult, así que supongo que está bien usar este enfoque.
Giorgi
87
¿Qué pasa si el objetivo es una actividad?
Fernando Gallego
99
Si el objetivo es la actividad, declararía la interfaz con un método como "void onActivityResult2 (int requestCode, int resultCode, Intent data)" y lo implementaría mediante una Activity. En DialogFragment simplemente obtengaActivity y verifique esta interfaz y llámela apropiadamente.
Ruslan Yanchyshyn
44
No es una buena solución. No funcionará después de guardar y restaurar el estado del fragmento de diálogo. LocalBroadcastManager es la mejor solución en este caso.
Nik
44
@ Nik Eso no es cierto. Es la mejor solución. No hay ningún problema al guardar y restaurar el estado. Si alguna vez tuvo un problema, utilizó el administrador de fragmentos incorrecto. El fragmento / llamante objetivo tiene que usar getChildFragmentManager () para mostrar el diálogo.
El increíble Jan
140

Como puede ver aquí, hay una manera muy simple de hacerlo.

En su DialogFragmentagregar un oyente de interfaz como:

public interface EditNameDialogListener {
    void onFinishEditDialog(String inputText);
}

Luego, agregue una referencia a ese oyente:

private EditNameDialogListener listener;

Esto se usará para "activar" los métodos de escucha, y también para verificar si la Actividad / Fragmento principal implementa esta interfaz (ver más abajo).

En el Activity/ FragmentActivity/ Fragmentque "llamó" DialogFragmentsimplemente implementan esta interfaz.

En DialogFragmenttodo lo que necesita agregar en el punto donde desea descartar DialogFragmenty devolver el resultado es este:

listener.onFinishEditDialog(mEditText.getText().toString());
this.dismiss();

¿Dónde mEditText.getText().toString()está lo que se pasará de nuevo a la llamada Activity?

Tenga en cuenta que si desea devolver algo más, simplemente cambie los argumentos que toma el oyente.

Finalmente, debe verificar si la interfaz fue implementada realmente por la actividad / fragmento principal:

@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");
    }
}

Esta técnica es muy flexible y permite volver a llamar con el resultado incluso si aún no desea cerrar el diálogo.

Assaf Gamliel
fuente
13
Esto funciona muy bien con Activity'sy FragmentActivity' s pero si la persona que llama es un Fragment?
Brais Gabin
1
No estoy seguro de entenderte completamente. Pero funcionará igual si la persona que llama es un Fragment.
Assaf Gamliel
2
Si la persona que llamó fue un Fragmententonces, puede hacer algunas cosas: 1. Pase el fragmento como referencia (puede que no sea una buena idea porque podría causar pérdidas de memoria). 2. Use FragmentManagery llame findFragmentByIdo findFragmentByTagobtendrá los fragmentos que existen en su actividad. Espero que haya ayudado. ¡Que tengas un gran día!
Assaf Gamliel
55
El problema con este enfoque es que los fragmentos no son muy buenos para retener objetos, ya que están destinados a ser recreados, por ejemplo, intente cambiar la orientación, el sistema operativo recreará el fragmento pero la instancia del oyente ya no estará disponible
Necronet
55
@LOG_TAG mira la respuesta de @ Timmmm. setTargetFragment()Y getTargetFragment()son mágicos.
Brais Gabin
48

Hay una forma mucho más simple de recibir un resultado de un DialogFragment.

Primero, en su Actividad, Fragmento o Fragmento de Actividad, necesita agregar la siguiente información:

@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.
         }
     }
}

El requestCodees básicamente la etiqueta int para el DialogFragment que llama, voy a mostrar cómo funciona esto en un segundo. ResultCode es el código que envía de regreso desde el DialogFragment que le dice a su Actividad, Fragmento o FragmentoActividad actual en espera lo que sucedió.

El siguiente fragmento de código que debe ingresar es la llamada al DialogFragment. Un ejemplo está aquí:

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");

Con estas tres líneas está declarando su DialogFragment, configurando un requestCode (que llamará a onActivityResult (...) una vez que se descarta el Diálogo, y luego está mostrando el diálogo. Es así de simple.

Ahora, en su DialogFragment solo necesita agregar una línea directamente antes de la dismiss()para que pueda enviar un resultCode de nuevo a onActivityResult ().

getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, getActivity().getIntent());
dismiss();

Eso es. Tenga en cuenta que el resultCode se define como el int resultCodeque he establecido resultCode = 1;en este caso.

Eso es todo, ahora puede enviar el resultado de su DialogFragment a su Actividad de llamada, Fragmento o FragmentActivity.

Además, parece que esta información se publicó anteriormente, pero no hubo un ejemplo suficiente dado, así que pensé en proporcionar más detalles.

EDITAR 06.24.2016 Pido disculpas por el código engañoso anterior. Pero ciertamente no puede recibir el resultado de vuelta a la actividad como la línea:

dialogFrag.setTargetFragment(this, 1);

establece un objetivo Fragmenty no Activity. Entonces, para hacer esto, debe usar Implementar un InterfaceCommunicator.

En su DialogFragmentconjunto una variable global

public InterfaceCommunicator interfaceCommunicator;

Crear una función pública para manejarlo

public interface InterfaceCommunicator {
    void sendRequestCode(int code);
}

Luego, cuando esté listo para enviar el código de nuevo Activitycuando DialogFragmenttermine de ejecutarse, simplemente agregue la línea antes de dismiss();su DialogFragment:

interfaceCommunicator.sendRequestCode(1); // the parameter is any int code you choose.

En su actividad ahora tiene que hacer dos cosas, la primera es eliminar esa línea de código que ya no es aplicable:

dialogFrag.setTargetFragment(this, 1);  

Luego implemente la interfaz y ya está. Puede hacerlo agregando la siguiente línea a la implementscláusula en la parte superior de su clase:

public class MyClass Activity implements MyDialogFragment.InterfaceCommunicator

Y luego @Overridela función en la actividad,

@Override
public void sendRequestCode(int code) {
    // your code here
}

Utiliza este método de interfaz como lo haría con el onActivityResult()método. Excepto que el método de interfaz es para DialogFragmentsy el otro es para Fragments.

Brandon
fuente
44
Este enfoque no funcionará si el objetivo es Actividad porque no puede llamarlo onActivityResult (desde su DialogFragment) debido al nivel de acceso protegido.
Ruslan Yanchyshyn
2
Eso simplemente no es cierto. Yo uso este código exacto en mis proyectos. Ahí es donde lo saqué y funciona bien. Recuerde que si tiene este problema de nivel de acceso protegido, puede cambiar su nivel de acceso para cualquier método y clase de protegido a privado o público si es necesario.
Brandon
66
Hola, dices que puedes llamar dialogFrag.setTargetFragment(this, 1)desde una Actividad, pero este método recibe un Fragmento como primer argumento, por lo que no se pudo lanzar. Estoy en lo cierto?
MondKin
1
Publicaré algunas respuestas para todos en unas pocas para explicar las cosas de la actividad.
Brandon
1
@Swift @lcompare probablemente necesite anular onAttach (Contexto context) en su DialogFragment. De este modo:@Override public void onAttach(Context context) { super.onAttach(context); yourInterface = (YourInterface) context; }
lidkxx
20

Bueno, es demasiado tarde para responder, pero esto es lo que hice para obtener resultados del DialogFragment. muy similar a la respuesta de @ brandon. Aquí estoy llamando DialogFragmentdesde un fragmento, simplemente coloque este código donde está llamando a su diálogo.

FragmentManager fragmentManager = getFragmentManager();
            categoryDialog.setTargetFragment(this,1);
            categoryDialog.show(fragmentManager, "dialog");

donde categoryDialoges mi al DialogFragmentque quiero llamar y después de esto en su implementación de dialogfragmentcolocar este código donde está configurando sus datos intencionalmente. El valor de resultCodees 1 puede configurarlo o usar Definido por sistema.

            Intent intent = new Intent();
            intent.putExtra("listdata", stringData);
            getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, intent);
            getDialog().dismiss();

ahora es el momento de volver al fragmento de llamada e implementar este método. comprobar la validez de los datos o dar como resultado el éxito si se desea con resultCodey requestCodeen caso de condiciones.

 @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();
    }
vikas kumar
fuente
10

Enfoque diferente, para permitir que un Fragment se comunique con su Actividad :

1) Defina una interfaz pública en el fragmento y cree una variable para él

public OnFragmentInteractionListener mCallback;

public interface OnFragmentInteractionListener {
    void onFragmentInteraction(int id);
}

2) Transmita la actividad a la variable mCallback en el fragmento

try {
    mCallback = (OnFragmentInteractionListener) getActivity();
} catch (Exception e) {
    Log.d(TAG, e.getMessage());
}

3) Implemente al oyente en su actividad

public class MainActivity extends AppCompatActivity implements DFragment.OnFragmentInteractionListener  {
     //your code here
}

4) Anular OnFragmentInteraction en la actividad

@Override
public void onFragmentInteraction(int id) {
    Log.d(TAG, "received from fragment: " + id);
}

Más información al respecto: https://developer.android.com/training/basics/fragments/communicating.html

lcompare
fuente
Gracias por resumirlo tan bien. Solo una nota para los demás, el tutorial de Android Devs sugiere anular public void onAttachel fragmento y hacer la actividad de lanzamiento allí
Big_Chair
8

Una forma fácil que encontré fue la siguiente: Implemente este es su cuadro de diálogo Fragmento,

  CallingActivity callingActivity = (CallingActivity) getActivity();
  callingActivity.onUserSelectValue("insert selected value here");
  dismiss();

Y luego, en la actividad que llamó al Fragmento de Diálogo, cree la función apropiada como tal:

 public void onUserSelectValue(String selectedValue) {

        // TODO add your implementation.
      Toast.makeText(getBaseContext(), ""+ selectedValue, Toast.LENGTH_LONG).show();
    }

El brindis es para demostrar que funciona. Trabajó para mi.

ZeWolfe15
fuente
No estoy seguro de si es una forma correcta de hacerlo, pero ciertamente funciona :)
soshial
Mejor uso en Interfacelugar de acoplamiento duro con clases concretas.
waqaslam
6

Estoy muy sorprendido al ver que nadie ha sugerido el uso de transmisiones locales para DialogFragmentque Activityla comunicación! Me parece mucho más simple y limpio que otras sugerencias. Esencialmente, se registra para Activityescuchar las transmisiones y envía las transmisiones locales desde sus DialogFragmentinstancias. Sencillo. Para obtener una guía paso a paso sobre cómo configurarlo todo, consulte aquí .

Adil Hussain
fuente
2
Me gusta esa solución, ¿se considera una buena o mejor práctica en Android?
Benjamin Scharbau
1
Realmente me gustó el tutorial, gracias por publicarlo. Quiero agregar que, dependiendo de lo que intente lograr, cualquiera de los métodos puede ser más útil que el otro. Sugeriría la ruta de transmisión local si tiene múltiples entradas / resultados que se envían a la actividad desde el cuadro de diálogo. Recomendaría usar la ruta onActivityResult si su salida es muy básica / simple. Entonces, para responder la pregunta de mejores prácticas, ¡depende de lo que intente lograr!
Brandon
1
@AdilHussain Tienes razón. Supuse que la gente estaba usando Fragmentos dentro de sus Actividades. La opción setTargetFragment es excelente si te estás comunicando con un Fragment y un DialogFragment. Pero debe usar el método Broadcast cuando se trata de una Actividad que llama al DialogFragment.
Brandon
3
¡Por amor a Foo, no uses transmisiones! Abre su aplicación a una serie de problemas de seguridad. También encuentro que la peor aplicación de Android que tengo para trabajar en transmisiones de abuso. ¿Se te ocurre una mejor manera de hacer que el código sea completamente inutilizable? ¿Ahora tengo que arrancar de raíz los receptores de difusión, en lugar de una línea de código CLARO? Para ser claros, existen usos para las costas anchas, ¡pero no en este contexto! ¡NUNCA en este contexto! Es simplemente descuidado. Local o no. Las devoluciones de llamada son todo lo que necesitas.
StarWind0
1
Además del EventBus de Guava, otra opción es el EventRus de GreenRobot . No he usado el EventBus de Guava pero he usado el EventBus de GreenRobot y he tenido una buena experiencia con él. Agradable y simple de usar. Para ver un pequeño ejemplo de cómo diseñar una aplicación de Android para usar el EventRus de GreenRobot, consulte aquí .
Adil Hussain
3

O comparta ViewModel como se muestra aquí:

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

Malaquiasz
fuente
2

En mi caso, necesitaba pasar argumentos a un targetFragment. Pero obtuve la excepción "Fragmento ya activo". Así que declaró una interfaz en mi DialogFragment que parentFragment implementó. Cuando parentFragment inició un DialogFragment, se estableció como TargetFragment. Luego en DialogFragment llamé

 ((Interface)getTargetFragment()).onSomething(selectedListPosition);
Lanitka
fuente
2

En 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()
            }
            
        }
    }
}

// Mi actividad

class MyActivity: AppCompatActivity(),FiltroDialogFragment.InterfaceCommunicator {

    override fun sendRequest(value: String) {
    // :)
    Toast.makeText(this, value, Toast.LENGTH_LONG).show()
    }
}
 

Espero que sirva, si puede mejorar, edítelo. Mi Inglés no es muy bueno

Irvin Joao
fuente
En mi caso, el diálogo se crea a partir de un fragmento, no de una actividad, por lo que esta solución no funciona. Pero me gusta la carita que pones :)
Ssenyonjo
0

si desea enviar argumentos y recibir el resultado del segundo fragmento, puede usar Fragment.setArguments para realizar esta tarea

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");
}
Sessy
fuente
-3

Solo para tenerlo como una de las opciones (ya que nadie lo mencionó aún): puede usar un autobús de eventos como Otto. Entonces en el diálogo que haces:

bus.post(new AnswerAvailableEvent(42));

Y haga que su interlocutor (Actividad o Fragmento) se suscriba:

@Subscribe public void answerAvailable(AnswerAvailableEvent event) {
   // TODO: React to the event somehow!
}
Dennis K
fuente