Receber resultado de DialogFragment

232

Estou usando DialogFragments para várias coisas: escolhendo itens da lista, inserindo texto.

Qual é a melhor maneira de retornar um valor (ou seja, uma string ou um item de uma lista) de volta à atividade / fragmento de chamada?

Atualmente, estou implementando a atividade de chamada DismissListenere fornecendo ao DialogFragment uma referência à atividade. O Diálogo chama o OnDimissmétodo na atividade e a atividade obtém o resultado do objeto DialogFragment. Muito bagunçado e não funciona na mudança de configuração (mudança de orientação), pois o DialogFragment perde a referência à atividade.

Obrigado por qualquer ajuda.

James Cross
fonte
10
DialogFragments ainda são apenas fragmentos. Sua abordagem é realmente a maneira recomendada para os fragmentos usarem para responder à atividade principal. developer.android.com/guide/topics/fundamentals/…
codinguser
1
Obrigado por isso. Eu estava muito perto (como você disse). A parte em que esse documento vinculado me ajudou foi usar onAttach () e converter a atividade para um ouvinte.
James Cross
2
@codinguser, @Styx - "dando a DialogFragment uma referência para a atividade" - este detalhe é um pouco arriscado, já que tanto o Activityeo DialogFragmentpode ser recriado. Usar o Activitypassado para onAttach(Activity activity)é a maneira correta e recomendada.
sstn

Respostas:

246

Use myDialogFragment.setTargetFragment(this, MY_REQUEST_CODE)no local em que você mostra a caixa de diálogo e, quando terminar, poderá chamar getTargetFragment().onActivityResult(getTargetRequestCode(), ...)e implementar onActivityResult()no fragmento que a contém.

Parece um abuso onActivityResult(), especialmente porque não envolve atividades. Mas eu já vi isso recomendado pelo pessoal oficial do google, e talvez até nas demos da API. Eu acho que é o que g/setTargetFragment()foi adicionado.

Timmmm
fonte
2
setTargetFragment menciona que o requestcode é para uso em onActivityResult, então acho que não há problema em usar essa abordagem.
Giorgi
87
E se o alvo for uma atividade?
Fernando Gallego
9
Se target for activity, eu declararia a interface com o método "void onActivityResult2 (int requestCode, int resultCode, Intent data)" e a implementaria por uma Activity. No DialogFragment, basta getActivity, verifique esta interface e chame-a adequadamente.
Ruslan Yanchyshyn
4
Não é uma boa solução. Ele não funcionará após salvar e restaurar o estado do fragmento de diálogo. LocalBroadcastManager é a melhor solução nesse caso.
Nik
4
@ Nik Isso não é verdade. É a melhor solução. Não há problema ao salvar e restaurar o estado. Se você já teve um problema, usou o gerenciador de fragmentos errado. O fragmento / chamador de destino precisa usar getChildFragmentManager () para mostrar a caixa de diálogo.
A incrível Jan
140

Como você pode ver aqui, há uma maneira muito simples de fazer isso.

No seu DialogFragmentadd, adicione um ouvinte de interface como:

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

Em seguida, adicione uma referência a esse ouvinte:

private EditNameDialogListener listener;

Isso será usado para "ativar" o (s) método (s) do ouvinte e também para verificar se a Atividade / Fragmento pai implementa essa interface (veja abaixo).

No Activity/ FragmentActivity/ Fragmentque "chamado" DialogFragmentsimplesmente implementa esta interface.

No seu DialogFragmenttudo, você precisa adicionar no ponto em que deseja descartar DialogFragmente retornar o resultado é o seguinte:

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

Onde mEditText.getText().toString()é o que será passado de volta para a chamada Activity.

Observe que, se você quiser retornar outra coisa, basta alterar os argumentos que o ouvinte recebe.

Por fim, você deve verificar se a interface foi realmente implementada pela atividade / fragmento pai:

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

Essa técnica é muito flexível e permite retornar com o resultado, mesmo que você não queira descartar a caixa de diálogo ainda.

Assaf Gamliel
fonte
13
Isso funciona muito bem com Activity's FragmentActivity', mas se o chamador é um Fragment?
precisa saber é o seguinte
1
Não sei se entendi completamente. Mas funcionará da mesma forma se o chamador for a Fragment.
Assaf Gamliel
2
Se o chamador era um Fragment, você pode fazer algumas coisas: 1. Passe o fragmento como referência (pode não ser uma boa ideia, pois pode causar vazamento de memória). 2. Use as FragmentManagerteclas findFragmentByIde, caso findFragmentByTagcontrário, obterá os fragmentos que existem em sua atividade. Espero que tenha ajudado. Tenha um ótimo dia!
amigos estão dizendo
5
O problema dessa abordagem é que o fragmento não é muito bom em reter objetos, pois eles devem ser recriados. Por exemplo, tente alterar a orientação, o sistema operacional recriará o fragmento, mas a instância do ouvinte não estará mais disponível
Necronet
5
@LOG_TAG veja a resposta do @ Timmmm. setTargetFragment()e getTargetFragment()são mágicos.
Brais Gabin 11/11/2013
48

Existe uma maneira muito mais simples de receber um resultado de um DialogFragment.

Primeiro, em sua Atividade, Fragmento ou FragmentActivity, você precisa adicionar as seguintes informações:

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

O requestCodebasicamente é seu rótulo int para o DialogFragment que você chamou, mostrarei como isso funciona em um segundo. O resultCode é o código que você envia de volta do DialogFragment, informando o que aconteceu com Activity, Fragment ou FragmentActivity em espera no momento.

O próximo trecho de código a ser inserido é a chamada para o DialogFragment. Um exemplo está aqui:

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

Com essas três linhas, você está declarando seu DialogFragment, configurando um requestCode (que chamará o onActivityResult (...) assim que o Diálogo for descartado e você estiver mostrando o diálogo. É simples assim.

Agora, em seu DialogFragment, você precisa apenas adicionar uma linha diretamente antes da, dismiss()para enviar um resultadoCode de volta para onActivityResult ().

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

É isso aí. Observe que o resultCode é definido como o int resultCodeque eu defini resultCode = 1;neste caso.

É isso, agora você pode enviar o resultado do seu DialogFragment de volta para a chamada Activity, Fragment ou FragmentActivity.

Além disso, parece que essas informações foram postadas anteriormente, mas não havia um exemplo suficiente, então pensei em fornecer mais detalhes.

EDIT 24.04.2016 Peço desculpas pelo código enganoso acima. Mas você certamente não pode receber o resultado de volta à atividade, visto como a linha:

dialogFrag.setTargetFragment(this, 1);

define um alvo Fragmente não Activity. Portanto, para fazer isso, você precisa usar o implement an InterfaceCommunicator.

No seu DialogFragmentconjunto, uma variável global

public InterfaceCommunicator interfaceCommunicator;

Crie uma função pública para lidar com isso

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

Então, quando você estiver pronto para enviar a parte de trás do código para o Activityquando o DialogFragmenté feito em execução, você simplesmente adicionar a linha antes de dismiss();o seu DialogFragment:

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

Na sua atividade, agora você precisa fazer duas coisas, a primeira é remover a linha de código que não é mais aplicável:

dialogFrag.setTargetFragment(this, 1);  

Em seguida, implemente a interface e pronto. Você pode fazer isso adicionando a seguinte linha à implementscláusula na parte superior da sua classe:

public class MyClass Activity implements MyDialogFragment.InterfaceCommunicator

E então @Overridea função na atividade,

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

Você usa esse método de interface como usaria o onActivityResult()método. Exceto que o método de interface é para DialogFragmentse o outro é para Fragments.

Brandon
fonte
4
Essa abordagem não funcionará se o destino for Activity, porque você não pode chamar onActivityResult (do seu DialogFragment) devido ao nível de acesso protegido.
Ruslan Yanchyshyn
2
Isso simplesmente não é verdade. Eu uso esse código exato em meus projetos. É aí que eu puxei e funciona muito bem. Lembre-se de que, se você está tendo esse problema de nível de acesso protegido, pode alterar seu nível de acesso para qualquer método e classe de protegido para privado ou público, se necessário.
Brandon
6
Olá, você diz que pode chamar dialogFrag.setTargetFragment(this, 1)de uma Atividade, mas esse método recebe um Fragmento como primeiro argumento, portanto, não foi possível converter. Estou certo ?
MondKin 02/10/2015
1
Vou postar algumas respostas para todos vocês em alguns, para explicar as atividades.
Brandon
1
@Swift @lcompare você provavelmente precisará substituir onAttach (contexto de contexto) no seu DialogFragment. Assim:@Override public void onAttach(Context context) { super.onAttach(context); yourInterface = (YourInterface) context; }
lidkxx
20

Bem, é tarde demais para ser a resposta, mas aqui está o que eu fiz para obter resultados do DialogFragment. muito semelhante à resposta de @ brandon. Aqui estou chamando DialogFragmentde um fragmento, basta colocar esse código onde você está chamando sua caixa de diálogo.

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

onde categoryDialogé o meu DialogFragmentque eu quero chamar e depois disso na sua implementação de dialogfragmentcolocar este código onde você está definindo seus dados na intenção. O valor de resultCodeé 1, você pode configurá-lo ou usar o sistema Definido.

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

agora é hora de voltar ao fragmento de chamada e implementar esse método. verifique a validade dos dados ou o sucesso do resultado, se desejar com resultCodee requestCodena condição if.

 @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
fonte
10

Abordagem diferente, para permitir que um fragmento se comunique até sua atividade :

1) Defina uma interface pública no fragmento e crie uma variável para ele

public OnFragmentInteractionListener mCallback;

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

2) Transmitir a atividade para a variável mCallback no fragmento

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

3) Implemente o ouvinte em sua atividade

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

4) Substitua o OnFragmentInteraction na atividade

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

Mais informações: https://developer.android.com/training/basics/fragments/communicating.html

lcompare
fonte
Obrigado por resumir isso tão bem. Apenas uma nota para outros, o tutorial Devs Android sugere substituir o public void onAttachdo fragmento e fazer o casting atividade lá
Big_Chair
8

Uma maneira fácil que encontrei foi a seguinte: implemente este é o seu dialogFragment,

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

E então, na atividade que chamou o Fragmento de Diálogo, crie a função apropriada como tal:

 public void onUserSelectValue(String selectedValue) {

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

O Toast é para mostrar que funciona. Trabalhou para mim.

ZeWolfe15
fonte
Não tenho a certeza se é uma maneira certa de fazê-lo, mas certamente funciona :)
soshial
Melhor uso Interfacedo que acoplamento rígido com classes de concreto.
waqaslam
6

Estou muito surpreso ao ver que ninguém sugeriu usando transmissões locais para DialogFragmenta Activitycomunicação! Acho que é muito mais simples e limpo do que outras sugestões. Essencialmente, você se registra para Activityouvir as transmissões e envia as transmissões locais de suas DialogFragmentinstâncias. Simples. Para um guia passo a passo sobre como configurar tudo, veja aqui .

Adil Hussain
fonte
2
Gosto dessa solução. Isso é considerado uma boa ou melhor prática no Android?
Benjamin Scharbau
1
Gostei muito do tutorial, obrigado por publicá-lo. Quero acrescentar que, dependendo do que você está tentando realizar, um dos métodos pode ser mais útil que o outro. Eu sugeriria a rota de transmissão local se você tiver várias entradas / resultados sendo enviados de volta à atividade a partir do diálogo. Eu recomendaria usar a rota onActivityResult se sua saída for muito básica / simples. Portanto, para responder à pergunta das melhores práticas, depende do que você está tentando realizar!
Brandon
1
@AdilHussain Você está certo. Eu assumi que as pessoas estavam usando fragmentos em suas atividades. A opção setTargetFragment é ótima se você estiver se comunicando com um fragmento e um DialogFragment. Mas você precisa usar o método Broadcast quando for uma Atividade chamando o DialogFragment.
Brandon
3
Pelo amor de Foo, não use transmissões !! Ele abre seu aplicativo para uma série de problemas de segurança. Também acho que o pior aplicativo para Android que tenho para trabalhar em transmissões de abuso. Você consegue pensar em uma maneira melhor de tornar o código completamente inutilizável? Agora estou tendo que erradicar os receptores de transmissão, em vez de uma linha de código CLEAR? Para ser claro, há usos para broadcoasts, mas não neste contexto! NUNCA neste contexto! É apenas desleixado. Local ou não. As chamadas de retorno são tudo o que você precisa.
precisa saber é o seguinte
1
Assim como o Guava EventBus, outra opção é o GreenRobot EventBus . Eu não usei o Guava EventBus, mas usei o GreenRobot EventBus e tive uma boa experiência com ele. Agradável e simples de usar. Para um pequeno exemplo de como arquitetar um aplicativo Android para usar o GreenRobot EventBus, consulte aqui .
Adil Hussain
3

Ou compartilhe o ViewModel como mostrado aqui:

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

Malachiasz
fonte
2

No meu caso, eu precisava passar argumentos para um targetFragment. Mas recebi a exceção "Fragmento já ativo". Por isso, declarei uma interface no meu DialogFragment que parentFragment implementou. Quando parentFragment iniciou um DialogFragment, ele se definiu como TargetFragment. Então, no DialogFragment, chamei

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

Na cidade 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()
            }
            
        }
    }
}

// Minha atividade

class MyActivity: AppCompatActivity(),FiltroDialogFragment.InterfaceCommunicator {

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

Espero que sirva, se você puder melhorar, edite-o. Meu inglês não é muito bom

Irvin Joao
fonte
No meu caso, a caixa de diálogo é criada a partir de um fragmento e não de uma atividade, portanto esta solução não funciona. Mas eu gosto do smiley que você coloca :)
Ssenyonjo
0

se você quiser enviar argumentos e receber o resultado do segundo fragmento, poderá usar Fragment.setArguments para realizar esta tarefa

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");
}
Yessy
fonte
-3

Apenas para tê-lo como uma das opções (já que ninguém o mencionou ainda) - você pode usar um barramento de eventos como o Otto. Então, na caixa de diálogo, você faz:

bus.post(new AnswerAvailableEvent(42));

E faça com que seu chamador (Atividade ou Fragmento) assine:

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