Обратный вызов фрагмента из диалогового окна

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

У меня есть

public class MyFragment extends Fragment implements OnClickListener 

Тогда в какой-то момент я мог бы сделать

 DialogFragment dialogFrag = MyDialogFragment.newInstance(this); dialogFrag.show(getFragmentManager, null); 

Где выглядит MyDialogFragment

 protected OnClickListener listener; public static DialogFragment newInstance(OnClickListener listener) { DialogFragment fragment = new DialogFragment(); fragment.listener = listener; return fragment; } 

Но нет никакой гарантии, что слушатель будет вокруг, если DialogFragment остановится и возобновится через свой жизненный цикл. Единственными гарантиями в Фрагменте являются те, которые прошли через Bundle через setArguments и getArguments.

Существует способ ссылки на действие, если он должен быть слушателем:

 public Dialog onCreateDialog(Bundle bundle) { OnClickListener listener = (OnClickListener) getActivity(); .... return new AlertDialog.Builder(getActivity()) ........ .setAdapter(adapter, listener) .create(); } 

Но я не хочу, чтобы Activity слушал события, мне нужен фрагмент. Действительно, это может быть любой объект Java, который реализует OnClickListener.

Рассмотрим конкретный пример фрагмента, который представляет AlertDialog через DialogFragment. Он имеет кнопки «Да / Нет». Как я могу отправить эти кнопки нажатием на Фрагмент, который его создал?

Solutions Collecting From Web of "Обратный вызов фрагмента из диалогового окна"

Занимаемая деятельность полностью не знает о DialogFragment.

Класс фрагмента:

 public class MyFragment extends Fragment { int mStackLevel = 0; public static final int DIALOG_FRAGMENT = 1; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { mStackLevel = savedInstanceState.getInt("level"); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt("level", mStackLevel); } void showDialog(int type) { mStackLevel++; FragmentTransaction ft = getActivity().getFragmentManager().beginTransaction(); Fragment prev = getActivity().getFragmentManager().findFragmentByTag("dialog"); if (prev != null) { ft.remove(prev); } ft.addToBackStack(null); switch (type) { case DIALOG_FRAGMENT: DialogFragment dialogFrag = MyDialogFragment.newInstance(123); dialogFrag.setTargetFragment(this, DIALOG_FRAGMENT); dialogFrag.show(getFragmentManager().beginTransaction(), "dialog"); break; } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch(requestCode) { case DIALOG_FRAGMENT: if (resultCode == Activity.RESULT_OK) { // After Ok code. } else if (resultCode == Activity.RESULT_CANCELED){ // After Cancel code. } break; } } } } 

Класс DialogFragment:

 public class MyDialogFragment extends DialogFragment { public static MyDialogFragment newInstance(int num){ MyDialogFragment dialogFragment = new MyDialogFragment(); Bundle bundle = new Bundle(); bundle.putInt("num", num); dialogFragment.setArguments(bundle); return dialogFragment; } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { return new AlertDialog.Builder(getActivity()) .setTitle(R.string.ERROR) .setIcon(android.R.drawable.ic_dialog_alert) .setPositiveButton(R.string.ok_button, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, getActivity().getIntent()); } } ) .setNegativeButton(R.string.cancel_button, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_CANCELED, getActivity().getIntent()); } }) .create(); } } 

Может быть, немного поздно, но может помочь другим людям с тем же вопросом, что и я.

Вы можете использовать setTargetFragment на Dialog перед показом, а в диалоге вы можете вызвать getTargetFragment чтобы получить ссылку.

Решение TargetFragment не кажется лучшим вариантом для фрагментов диалога, поскольку оно может создавать IllegalStateException после того, как приложение будет уничтожено и воссоздано. В этом случае FragmentManager не смог найти целевой фрагмент, и вы получите IllegalStateException с таким сообщением:

«Фрагмент больше не существует для ключевого андроида: target_state: index 1"

Кажется, что Fragment#setTargetFragment() не предназначен для связи между дочерним и родительским фрагментами, а скорее для связи между sibling-Fragments.

Таким образом, альтернативный способ заключается в создании фрагментов диалога, подобных этому, с помощью ChildFragmentManager родительского фрагмента, а не с использованием действий FragmentManager :

 dialogFragment.show(ParentFragment.this.getChildFragmentManager(), "dialog_fragment"); 

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

 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); try { callback = (Callback) getParentFragment(); } catch (ClassCastException e) { throw new ClassCastException("Calling fragment must implement Callback interface"); } } 

Осталось только вызывать метод обратного вызова после этих шагов.

Для получения дополнительной информации о проблеме вы можете проверить ссылку: https://code.google.com/p/android/issues/detail?id=54520

В руководстве « Общение с другими фрагментами» говорится, что фрагменты должны взаимодействовать через связанную с ним деятельность .

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

Я выполнил эти простые шаги, чтобы сделать это.

  1. Создайте интерфейс, например DialogFragmentCallbackInterface с помощью некоторого метода, такого как callBackMethod(Object data) . Который вы призвали бы передавать данные.
  2. Теперь вы можете реализовать интерфейс DialogFragmentCallbackInterface в своем фрагменте, например MyFragment implements DialogFragmentCallbackInterface
  3. Во время создания DialogFragment установите ваш вызывающий фрагмент MyFragment качестве целевого фрагмента, который создал DialogFragment используя myDialogFragment.setTargetFragment(this, 0) check setTargetFragment (фрагмент фрагмента, int requestCode)

     MyDialogFragment dialogFrag = new MyDialogFragment(); dialogFrag.setTargetFragment(this, 1); 
  4. Получите целевой объект-фрагмент в свой DialogFragment , вызвав getTargetFragment() и DialogFragmentCallbackInterface его на DialogFragmentCallbackInterface Теперь вы можете использовать этот интерфейс для отправки данных в ваш фрагмент.

     DialogFragmentCallbackInterface callback = (DialogFragmentCallbackInterface) getTargetFragment(); callback.callBackMethod(Object data); 

    Вот и все! Просто убедитесь, что вы реализовали этот интерфейс в своем фрагменте.

Вы должны определить interface в своем классе фрагментов и реализовать этот интерфейс в своей родительской деятельности. Подробности описаны здесь http://developer.android.com/guide/components/fragments.html#EventCallbacks . Код будет выглядеть так:

Фрагмент:

 public static class FragmentA extends DialogFragment { OnArticleSelectedListener mListener; // Container Activity must implement this interface public interface OnArticleSelectedListener { public void onArticleSelected(Uri articleUri); } @Override public void onAttach(Activity activity) { super.onAttach(activity); try { mListener = (OnArticleSelectedListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener"); } } } 

Мероприятия:

 public class MyActivity extends Activity implements OnArticleSelectedListener{ ... @Override public void onArticleSelected(Uri articleUri){ } ... } 

У меня была аналогичная проблема. Решение, которое я выяснил, было:

  1. Объявите интерфейс в вашем DialogFragment точно так же, как объяснил Джеймс МакКракен.

  2. Внедрите интерфейс в своей деятельности (не фрагмент! Это не очень хорошая практика).

  3. Из метода обратного вызова в вашей деятельности вызовите требуемую публичную функцию в ваш фрагмент, который выполняет задание, которое вы хотите выполнить.

Таким образом, он становится двухэтапным процессом: DialogFragment -> Activity, а затем Activity -> Fragment

Я получаю результат для Fragment DashboardLiveWall (вызывающий фрагмент) из Fragment LiveWallFilterFragment (получающий фрагмент). Подобно этому …

  LiveWallFilterFragment filterFragment = LiveWallFilterFragment.newInstance(DashboardLiveWall.this ,""); getActivity().getSupportFragmentManager().beginTransaction(). add(R.id.frame_container, filterFragment).addToBackStack("").commit(); 

где

 public static LiveWallFilterFragment newInstance(Fragment targetFragment,String anyDummyData) { LiveWallFilterFragment fragment = new LiveWallFilterFragment(); Bundle args = new Bundle(); args.putString("dummyKey",anyDummyData); fragment.setArguments(args); if(targetFragment != null) fragment.setTargetFragment(targetFragment, KeyConst.LIVE_WALL_FILTER_RESULT); return fragment; } 

SetResult обратно к вызывающему фрагменту, как

 private void setResult(boolean flag) { if (getTargetFragment() != null) { Bundle bundle = new Bundle(); bundle.putBoolean("isWorkDone", flag); Intent mIntent = new Intent(); mIntent.putExtras(bundle); getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, mIntent); } } 

onActivityResult

 @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == Activity.RESULT_OK) { if (requestCode == KeyConst.LIVE_WALL_FILTER_RESULT) { Bundle bundle = data.getExtras(); if (bundle != null) { boolean isReset = bundle.getBoolean("isWorkDone"); if (isReset) { } else { } } } } } 

Обновлено:

Я создал библиотеку на основе моего кода gist, который генерирует эти кастинга для вас, используя @CallbackFragment и @Callback .

https://github.com/zeroarst/callbackfragment .

И пример дает вам пример, который отправляет обратный вызов из фрагмента в другой фрагмент.

Старый ответ:

Я сделал BaseCallbackFragment и аннотацию @FragmentCallback . В настоящее время он расширяет Fragment , вы можете изменить его на DialogFragment и будете работать. Он проверяет реализации в следующем порядке: getTargetFragment ()> getParentFragment ()> контекст (активность).

Затем вам просто нужно расширить его и объявить свои интерфейсы в своем фрагменте и дать ему аннотацию, а базовый фрагмент сделает все остальное. В аннотации также есть параметр, mandatory для вас, чтобы определить, хотите ли вы заставить фрагмент реализовать обратный вызов.

 public class EchoFragment extends BaseCallbackFragment { private FragmentInteractionListener mListener; @FragmentCallback public interface FragmentInteractionListener { void onEcho(EchoFragment fragment, String echo); } } 

https://gist.github.com/zeroarst/3b3f32092d58698a4568cdb0919c9a93

Я решил это элегантным способом с RxAndroid. Получите наблюдателя в конструкторе DialogFragment и подпишите наблюдаемый и нажмите значение при вызове обратного вызова. Затем в вашем фрагменте создайте внутренний класс Observer, создайте экземпляр и передайте его в конструкторе диалогового окна. Я использовал WeakReference в наблюдателе, чтобы избежать утечек памяти. Вот код:

BaseDialogFragment.java

 import java.lang.ref.WeakReference; import io.reactivex.Observer; public class BaseDialogFragment<O> extends DialogFragment { protected WeakReference<Observer<O>> observerRef; protected BaseDialogFragment(Observer<O> observer) { this.observerRef = new WeakReference<>(observer); } protected Observer<O> getObserver() { return observerRef.get(); } } 

DatePickerFragment.java

 public class DatePickerFragment extends BaseDialogFragment<Integer> implements DatePickerDialog.OnDateSetListener { public DatePickerFragment(Observer<Integer> observer) { super(observer); } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { // Use the current date as the default date in the picker final Calendar c = Calendar.getInstance(); int year = c.get(Calendar.YEAR); int month = c.get(Calendar.MONTH); int day = c.get(Calendar.DAY_OF_MONTH); // Create a new instance of DatePickerDialog and return it return new DatePickerDialog(getActivity(), this, year, month, day); } @Override public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) { if (getObserver() != null) { Observable.just(month).subscribe(getObserver()); } } } 

MyFragment.java

 //Show the dialog fragment when the button is clicked @OnClick(R.id.btn_date) void onDateClick() { DialogFragment newFragment = new DatePickerFragment(new OnDateSelectedObserver()); newFragment.show(getFragmentManager(), "datePicker"); } //Observer inner class private class OnDateSelectedObserver implements Observer<Integer> { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(Integer integer) { //Here you invoke the logic } @Override public void onError(Throwable e) { } @Override public void onComplete() { } } 

Вы можете увидеть исходный код здесь: https://github.com/andresuarezz26/carpoolingapp

Правильный способ установки слушателя на фрагмент – это установить его, когда он подключен . Проблема была в том, что onAttachFragment никогда не вызывался, после некоторого расследования я понял, что использовал getFragmentManager вместо getChildFragmentManager

Вот как я это делаю:

 MyDialogFragment dialogFragment = MyDialogFragment.newInstance("title", "body"); dialogFragment.show(getChildFragmentManager(), "SOME_DIALOG"); 

Прикрепите его в onAttachFragment:

 @Override public void onAttachFragment(Fragment childFragment) { super.onAttachFragment(childFragment); if (childFragment instanceof MyDialogFragment) { MyDialogFragment dialog = (MyDialogFragment) childFragment; dialog.setListener(new MyDialogFragment.Listener() { @Override public void buttonClicked() { } }); } }