Получите результат от DialogFragment

Я использую DialogFragments для ряда вещей: выбор элемента из списка, ввод текста.

Каков наилучший способ вернуть значение (то есть строку или элемент из списка) обратно в вызывающую активность / фрагмент?

В настоящее время я выполняю звонки DismissListener и предоставление DialogFragment ссылки на действие. Диалог тогда вызывает OnDimiss метод в действии, и действие получает результат из объекта DialogFragment. Очень грязно и не работает при изменении конфигурации (изменении ориентации), так как DialogFragment теряет ссылку на действие.

Спасибо за любую помощь.

17 ответов

Решение

Использование myDialogFragment.setTargetFragment(this, MY_REQUEST_CODE) из того места, где вы показываете диалог, а затем, когда ваш диалог закончен, из него вы можете позвонить getTargetFragment().onActivityResult(getTargetRequestCode(), ...)и реализовать onActivityResult() в содержащем фрагмент.

Это похоже на злоупотребление onActivityResult()тем более, что это вообще не связано с деятельностью. Но я видел его рекомендованным официальными людьми Google, и, возможно, даже в демоверсиях API. Я думаю это то, что g/setTargetFragment() были добавлены для.

Как вы можете видеть здесь, есть очень простой способ сделать это.

В вашем DialogFragment добавить интерфейс слушателя, например:

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

Затем добавьте ссылку на этого слушателя:

private EditNameDialogListener listener;

Это будет использоваться для "активации" метода (ов) слушателя, а также для проверки того, реализует ли родительский Activity/Fragment этот интерфейс (см. Ниже).

в Activity/FragmentActivity/Fragment что "называется" DialogFragment просто реализуйте этот интерфейс.

В вашем DialogFragment все, что вам нужно добавить в тот момент, когда вы хотели бы уволить DialogFragment и вернуть результат так:

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

куда mEditText.getText().toString() это то, что будет передано обратно к призванию Activity,

Обратите внимание, что если вы хотите вернуть что-то еще, просто измените аргументы, которые принимает слушатель.

Наконец, вы должны проверить, был ли интерфейс фактически реализован родительским действием / фрагментом:

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

Этот метод очень гибкий и позволяет перезвонить с результатом, даже если вы еще не хотите закрывать диалог.

Существует гораздо более простой способ получить результат от DialogFragment.

Во-первых, в вашу активность, фрагмент или действие фрагмента необходимо добавить следующую информацию:

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

requestCode это в основном ваша int-метка для DialogFragment, который вы назвали, я покажу, как это работает через секунду. ResultCode - это код, который вы отправляете обратно из DialogFragment, сообщая вашей текущей ожидающей активности, фрагменту или фрагменту активности, что произошло.

Следующий фрагмент кода - это вызов DialogFragment. Пример здесь:

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

С помощью этих трех строк вы объявляете свой DialogFragment, устанавливая requestCode (который будет вызывать onActivityResult (...) после закрытия Dialog, а затем вы показываете диалоговое окно. Это так просто.

Теперь в вашем DialogFragment вам нужно просто добавить одну строку непосредственно перед dismiss() так что вы отправляете resultCode обратно в onActivityResult().

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

Вот и все. Обратите внимание, resultCode определяется как int resultCode который я поставил resultCode = 1; в этом случае.

Вот и все, теперь вы можете отправить результат вашего DialogFragment обратно в вызывающую активность, фрагмент или FragmentActivity.

Кроме того, похоже, что эта информация была опубликована ранее, но не было приведено достаточного примера, поэтому я подумал предоставить более подробную информацию.

РЕДАКТИРОВАТЬ 06.24.2016 Я прошу прощения за вводящий в заблуждение код выше. Но вы, безусловно, не можете получить результат обратно к активности, видя в виде строки:

dialogFrag.setTargetFragment(this, 1);

ставит цель Fragment и не Activity, Таким образом, чтобы сделать это, вам нужно использовать реализовать InterfaceCommunicator,

В вашем DialogFragment установить глобальную переменную

public InterfaceCommunicator interfaceCommunicator;

Создайте публичную функцию для обработки

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

Затем, когда вы будете готовы отправить код обратно Activity когда DialogFragment После запуска вы просто добавляете строку перед вами dismiss(); ваш DialogFragment:

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

Теперь в своей деятельности вы должны сделать две вещи, во-первых, удалить одну строку кода, которая больше не применяется:

dialogFrag.setTargetFragment(this, 1);  

Затем реализуйте интерфейс, и все готово. Вы можете сделать это, добавив следующую строку в implements предложение в самом верху вашего класса:

public class MyClass Activity implements MyDialogFragment.InterfaceCommunicator

А потом @Override функция в деятельности,

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

Вы используете этот метод интерфейса так же, как onActivityResult() метод. За исключением того, что метод интерфейса предназначен для DialogFragments а другой для Fragments,

Для тех, кто все еще читает это: setTargetFragment()устарела. Теперь рекомендуется использоватьFragmentResultListener API вроде этого:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setFragmentResultListener("requestKey") { key, bundle ->
        val result = bundle.getString("resultKey")
        // Do something with the result...
    }

    ...
    
    // Somewhere show your dialog
    MyDialogFragment.newInstance().show(parentFragmentManager, "tag")
}

Тогда в вашем MyDialogFragment установить результат:

button.setOnClickListener{
    val result = "some string"
    setFragmentResult("requestKey", bundleOf("resultKey" to result))
    dismiss()
}

Ну, может быть слишком поздно, чтобы ответить, но вот что я сделал, чтобы получить результаты от DialogFragment, очень похоже на ответ @brandon. Вот я звоню DialogFragment из фрагмента просто поместите этот код туда, где вы вызываете диалог.

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

где categoryDialog мой DialogFragment который я хочу позвонить и после этого в вашей реализации dialogfragment разместите этот код там, где вы устанавливаете свои данные намеренно. Значение resultCode это 1, вы можете установить его или использовать систему, определенную.

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

Теперь пришло время вернуться к вызывающему фрагменту и реализовать этот метод. проверьте правильность данных или успех результата, если вы хотите с resultCode а также requestCode в случае если.

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

Другой подход, позволяющий Фрагменту общаться до его Активности:

1) Определите публичный интерфейс во фрагменте и создайте для него переменную

public OnFragmentInteractionListener mCallback;

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

2) Приведите активность к переменной mCallback во фрагменте.

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

3) Реализуйте слушателя в своей деятельности

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

4) Переопределить OnFragmentInteraction в действии

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

Больше информации об этом: https://developer.android.com/training/basics/fragments/communicating.html

Один простой способ, который я нашел, заключался в следующем: реализовать это ваш dialogFragment,

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

А затем в действии, которое вызвало фрагмент диалога, создайте соответствующую функцию как таковую:

 public void onUserSelectValue(String selectedValue) {

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

Тост должен показать, что он работает. Работал на меня.

Я очень удивлен, увидев, что никто не предложил использовать местные трансляции для DialogFragment в Activity общение! Я считаю, что это намного проще и чище, чем другие предложения. По сути, вы регистрируетесь на Activity прослушивать трансляции, и вы отправляете местные трансляции с вашего DialogFragment экземпляров. Просто. Пошаговое руководство по настройке всего этого см. Здесь.

Или поделитесь ViewModel, как показано здесь:

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

В котлине

// My DialogFragment

Класс 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()
        }

    }
}

}

// Моя активность

класс MyActivity: AppCompatActivity (), FiltroDialogFragment.InterfaceCommunicator {

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

}

Я надеюсь, что это работает, если вы можете улучшить, пожалуйста, отредактируйте его. Мой английский не очень хорош

В моем случае мне нужно было передать аргументы в targetFragment. Но я получил исключение "Фрагмент уже активен". Поэтому я объявил интерфейс в моем DialogFragment, который реализовал parentFragment. Когда parentFragment запускает DialogFragment, он устанавливает себя как TargetFragment. Затем в DialogFragment я позвонил

 ((Interface)getTargetFragment()).onSomething(selectedListPosition);

Если вы хотите отправить аргументы и получить результат из второго фрагмента, вы можете использовать Fragment.setArguments для выполнения этой задачи

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

Различные подходы (2023 г.):

  • Использование графа навигации: просто установите аргумент для направления возврата (от фрагмента диалога к его родительскому элементу) и получите его в пункт назначения.

Отправить из диалога (java):NavHostFragment.navigate(this, YourDialogName+Directions .action+---+To---(DefinedArgument)));

Получите в родителе (java):dialogResult = NameOfFragmentArgs.fromBundle(requireArguments()).get+DefinedArgument;

  • использование графика навигации и ViewModel+LiveData (рекомендуется)

для этого подхода есть полный пример на веб-сайте CommonsWare.

  • Использование намерения и пакета

  • Используя собственный пользовательский интерфейс для диалога , вы получаете прямой доступ кsetOnClickListenerметод для положительных и отрицательных кнопок внутри фрагмента;

  • Реализация определенного интерфейса, напримерsetOnYesв целевом фрагменте/действии

  • С использованием FragmentManagerи setFragmentResult(String, Bundle) Посмотреть этот пост

Отмеченный ответ устарел. Используйте новый API результатов фрагментов

но вместо:

      override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    
    setFragmentResultListener("requestKey") { requestKey, bundle ->
        val result = bundle.getString("bundleKey")
    }
}

которые работают для действий и стандартной связи между корневыми фрагментами, используйте:

      override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    
 childFragmentManager.setFragmentResultListener("requestKey",this/*lifecycleOwner*/) { requestKey, bundle ->
        val result = bundle.getString("bundleKey")
    }
}

и покажиdialogFragmentнравиться:

      MyDialogFragment().show(childFragmentManager, "tag")

TL;DR — используйте этот класс AppDialog как для передачи данных в DialogFragment, так и для получения результата.

Детальное объяснение:

  • Предпосылка — Фрагменты уничтожаются и воссоздаются при изменении конфигурации. Просмотрите модели. При использовании Dialog рекомендуется обернуть его в DialogFragment, чтобы, когда пользователь поворачивает устройство и меняет ориентацию, Dialog не исчезал неожиданно (DialogFragment воссоздаст его и повторно отобразит).
  • Ограничение (отсюда и этот вопрос). Способ работы DialogFragment заключается в том, что он берет класс, который ему нужно будет повторно создать при изменении конфигурации, - это означает, что нельзя иметь параметры конструктора для подкласса для передачи параметров, и обычно нужно сделать пользовательские обратные вызовы через модель представления, чтобы передать результат диалога. Обычно это означает новый подкласс для каждого диалога.
  • Решение. Чтобы помочь со всем этим, на помощь приходит этот пользовательский фрагмент AppDialog — параметры хранятся в памяти (аналогично модели представления, вы можете думать о ней как о крошечной пользовательской модели представления, которая хранит T в памяти и использует ее для пересоздания диалога об изменении конфигурации) до тех пор, пока фрагмент диалога не будет закрыт. Правильный способ обратного вызова - через модель представления. Если фрагмент, который показывает AppDialog, то у вас, вероятно, уже есть модель представления, и вы можете ссылаться на нее из лямбды, используемой для создания диалога, — это означает дополнительную сильную ссылку на модель представления, пока фрагмент диалога не будет отклонен.
  • Пример — см. примеры, в которых простой Dialog подвергается рефакторингу, чтобы использовать этот служебный класс AppDialog как для получения параметра, так и для обратного вызова viewModel для уведомления о результате.

Вспомогательный класс:

      
class AppDialog<T>: DialogFragment() {
    companion object {

        fun<T> buildDialog(params: T? = null, builder: AppDialogLambda<T>): AppDialog<T> {

            // Setup arguments
            val args = Bundle()
            args.putInt("key", pushDialogArgs(params, builder))

            // Instantiate
            val fragment = AppDialog<T>()
            fragment.arguments = args
            return fragment
        }

        // --------------------
        // Dialog Arguments

        private var lastKey: Int = 0
        private val dialogArgs = mutableMapOf<Int, Pair<Any?, AppDialogLambda<*>>>()

        private fun pushDialogArgs(params: Any?, builder: AppDialogLambda<*>): Int {
            dialogArgs[lastKey] = params to builder
            return lastKey++
        }

        private fun getDialogArgs(key: Int): Pair<Any?, AppDialogLambda<*>> {
            return dialogArgs[key]!!
        }

        private fun deleteDialogArgs(key: Int) {
            dialogArgs.remove(key)
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        // Get arguments
        val argKey = requireArguments().getInt("key")
        val (params, builder) = getDialogArgs(argKey)

        // We are getting back our arguments we passed AppDialog.buildDialog and
        // the type is guaranteed to be the same. Silence this warning
        @Suppress("UNCHECKED_CAST")
        return (builder as AppDialogLambda<T>)(this, params as T?)
    }

    override fun onDismiss(dialog: DialogInterface) {
        super.onDismiss(dialog)
        val argKey = requireArguments().getInt("key")
        deleteDialogArgs(argKey)
    }
}

Пример использования ( после ):

              val info = mapOf("message" to "${error.description}\n\nPlease check your Internet connection and try again.")
        AppDialog.buildDialog(info) { fragment, params ->
            fragment.isCancelable = false // since we are in a DialogFragment
            AlertDialog.Builder(fragment.context)
                .setTitle("Terms Of Service Failed To Load")
                .setMessage(params!!["message"])
                .setPositiveButton("Retry") { _, _ ->
                    // Update the view model instead of calling UserTOSFragment directly 
                    // as the fragment may be destroyed and recreated
                    // on configuration changes. The viewModel will stay alive.
                    viewModel.onTermsOfServiceReload()
                }
                .setNegativeButton("Cancel") { _, _ ->
                    viewModel.onTermsOfServiceDeclined()
                    fragment.findNavController().popBackStack()
                }.create()
        }.show(parentFragmentManager, "TOS Failed Dialog")

Пример использования ( до ): без использования DialogFragment(для иллюстрации не делайте этого, это плохая практика, так как диалоговое окно будет уничтожено при изменении конфигурации), код внутри UserTOSFragment.kt — обратите внимание на код, используемый для прямого вызова UserTOSFragment. loadContent() при повторной попытке. Это нужно переписать, чтобы вместо этого вызывать viewModel.onTermsOfServiceDeclined() в приведенном выше примере:

              AlertDialog.Builder(context)
            .setTitle("Terms Of Service Failed To Load")
            .setMessage("${error.description}\n\nPlease check your Internet connection and try again.")
            .setPositiveButton("Retry") { _, _ ->
                loadContent()
            }
            .setCancelable(false)
            .setNegativeButton("Cancel") { _, _ ->
                viewModel.onTermsOfServiceDeclined()
                findNavController().popBackStack()
            }
            .show()

На фрагменте диалога

      class AbcDialogFragment(private val ondata: (data: String) -> Unit) :  DialogFragment() {}

На фрагменте хоста / Активность

           val abcDialogFragment = AbcDialogFragment(ondata = {data->  })

                a. 
     bcDialogFragment.show(requireActivity().supportFragmentManager,
                    "TAG")

а во фрагменте диалога вы можете вызвать onData, когда фрагмент диалога закрыт, или любые прослушиватели щелчков.

Просто чтобы это было одним из вариантов (так как никто еще не упомянул об этом) - вы можете использовать шину событий, такую ​​как Otto. Итак, в диалоге вы делаете:

bus.post(new AnswerAvailableEvent(42));

И пусть ваш абонент (Activity или Fragment) подписывается на него:

@Subscribe public void answerAvailable(AnswerAvailableEvent event) {
   // TODO: React to the event somehow!
}
Другие вопросы по тегам