Обратный вызов фрагмента из диалогового фрагмента
вопрос: Как создать обратный вызов из диалогового фрагмента в другой фрагмент. В моем случае вовлеченная деятельность должна быть полностью не осведомлена о DialogFragment.
считайте, что у меня есть
public class MyFragment extends Fragment implements OnClickListener
потом в какой-то момент я может do
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 приостанавливается и возобновляется через жизненный цикл. Единственные гарантии в фрагменте-это те, которые передаются через пакет через setArguments и getArguments.
есть способ ссылаться на активность, если это должен быть слушатель:
public Dialog onCreateDialog(Bundle bundle) {
OnClickListener listener = (OnClickListener) getActivity();
....
return new AlertDialog.Builder(getActivity())
........
.setAdapter(adapter, listener)
.create();
}
но я не хочу, чтобы активность прослушивала события, мне нужен фрагмент. Действительно, это может быть любой объект Java, который реализует OnClickListener.
рассмотрим конкретный пример фрагмента, который представляет AlertDialog через DialogFragment. Он имеет Да / нет кнопки. Как я могу отправить эти нажатия кнопок обратно к фрагменту, который его создал?
11 ответов:
вовлеченная деятельность полностью не знает о 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(); } }
TargetFragment решение не кажется лучшим вариантом для диалоговых фрагментов, потому что он может создать
IllegalStateExceptionпосле того, как приложение будет уничтожено и воссоздано. В данном случаеFragmentManagerне удалось найти целевой фрагмент, и вы получитеIllegalStateExceptionС таким сообщением:"фрагмент больше не существует для ключевого android: target_state: индекс 1"
кажется
Fragment#setTargetFragment()не предназначен для связи между дочерним и родительским фрагментом, но скорее для общения между братьями и сестрами-фрагментами.таким образом, альтернативный способ заключается в создании диалоговых фрагментов, как это с помощью
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
может быть, немного поздно, но могут помочь другим людям с тем же вопросом, как я сделал.
можно использовать
setTargetFragmentonDialogперед показом, и в диалоговом окне вы можете вызватьgetTargetFragmentполучить ссылку.
я следовал этим простым шагам, чтобы сделать это.
- создать интерфейс, как
DialogFragmentCallbackInterfaceС некоторым методом, какcallBackMethod(Object data). Который вы бы позвонили, чтобы передать данные.- теперь вы можете реализовать
DialogFragmentCallbackInterfaceинтерфейс в вашем фрагменте, какMyFragment implements DialogFragmentCallbackInterfaceво время
DialogFragmentсоздание установите вызывающий фрагментMyFragmentкак целевой фрагмент, который создалDialogFragmentиспользоватьmyDialogFragment.setTargetFragment(this, 0)Регистрация setTargetFragment (фрагмент фрагмент, int requestCode)MyDialogFragment dialogFrag = new MyDialogFragment(); dialogFrag.setTargetFragment(this, 1);получить целевой объект фрагмента в ваш
DialogFragmentпо телефонуgetTargetFragment()иDialogFragmentCallbackInterface.Теперь вы можете использовать этот интерфейс для отправки данных в ваш фрагмент.DialogFragmentCallbackInterface callback = (DialogFragmentCallbackInterface) getTargetFragment(); callback.callBackMethod(Object data);вот и все сделано! просто убедитесь, что вы реализовали этот интерфейс в своем фрагменте.
The связь с другими фрагментами руководство говорит, что осколки должны общаться через соответствующую деятельность.
часто вы хотите, чтобы один фрагмент общался с другим, например пример изменения содержимого на основе события пользователя. Все Фрагмент к фрагменту коммуникация осуществляется через связанные Деятельность. Два фрагмента никогда не должны общаться напрямую.
вы должны определить
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){ } ... }
Я столкнулся с подобной проблемой. Решение, которое я узнал, было:
объявите интерфейс в вашем DialogFragment так же, как Джеймс Маккракен объяснил выше.
реализовать интерфейс в вашей деятельности (не фрагмент! Это не очень хорошая практика).
из метода обратного вызова в вашей деятельности вызовите необходимую публичную функцию в вашем фрагменте, которая выполняет задание, которое вы хотите делать.
таким образом, он становится двухэтапным процессом : DialogFragment- > Activity и затем Activity - > Fragment
Я получаю результат на фрагмент DashboardLiveWall(вызывающий фрагмент) из фрагмента 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 () > context (activity).тогда вам просто нужно расширить его и объявить ваши интерфейсы в вашем фрагменте и дать ему аннотацию, а базовый фрагмент сделает все остальное. Аннотация также имеет параметр
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 и suscribe для observable и нажмите значение при вызове обратного вызова. Затем в вашем фрагменте создайте внутренний класс наблюдателя, создайте экземпляр и передайте его в конструктор DialogFragment. Я использовал 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() { } }); } }
Comments