Обратный вызов фрагмента из диалогового окна
Вопрос: Как создать обратный вызов из диалога 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. Он имеет кнопки "Да/Нет". Как я могу отправить эти кнопки нажатием на Фрагмент, который его создал?
Ответы
Ответ 1
Занимаемая деятельность полностью не знает о диалоговом диалоге.
Класс фрагмента:
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();
}
}
Ответ 2
Решение 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
Ответ 3
Возможно, немного поздно, но может помочь другим людям с тем же вопросом, что и я.
Вы можете использовать setTargetFragment
на Dialog
перед показом, а в диалоговом окне вы можете вызвать getTargetFragment
, чтобы получить ссылку.
Ответ 4
Я выполнил эти простые шаги, чтобы сделать это.
- Создайте интерфейс, например
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);
Это все! просто убедитесь, что вы реализовали этот интерфейс в своем фрагменте.
Ответ 5
В "Общение с другими фрагментами" говорится, что фрагменты должны взаимодействовать через связанное с ним действие.
Часто вам нужно, чтобы один фрагмент связывался с другим, для например, для изменения содержимого на основе пользовательского события. Все Обмен фрагмента-фрагмента осуществляется через связанные Мероприятия. Два фрагмента никогда не должны связываться напрямую.
Ответ 6
Вы должны определить interface
в своем классе фрагмента и реализовать этот интерфейс в своей родительской деятельности. Подробности здесь описаны http://developer.android.com/guide/components/fragments.html#EventCallbacks. Код будет выглядеть так:
Fragment:
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){
}
...
}
Ответ 7
У меня возникла аналогичная проблема. Решение, которое я выяснил, было:
-
Объявите интерфейс в вашем DialogFragment точно так же, как объяснил Джеймс МакКракен.
-
Внедрите интерфейс в свою деятельность (не фрагмент! Это не очень хорошая практика).
-
Из метода обратного вызова в вашей деятельности вызовите требуемую публичную функцию в свой фрагмент, который выполняет задание, которое вы хотите сделать.
Таким образом, он становится двухэтапным процессом: DialogFragment → Activity, а затем Activity → Fragment
Ответ 8
Я получаю результат для 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 {
}
}
}
}
}
Ответ 9
Обновлено:
Я создал библиотеку на основе моего 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
Ответ 10
Правильный способ настроить слушателя на фрагмент - установить его, когда он прикреплен. Проблема у меня была в том, что 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() {
}
});
}
}
Ответ 11
Согласно официальной документации:
Фрагмент # setTargetFragment
Необязательная цель для этого фрагмента. Это может быть использовано, например, если этот фрагмент запускается другим, и когда он закончится, хочет вернуть результат первому. Заданная цель сохраняется во всех экземплярах с помощью FragmentManager # putFragment.
Фрагмент # getTargetFragment
Вернуть целевой фрагмент, установленный setTargetFragment (Fragment, int).
Так что вы можете сделать это:
// In your fragment
public class MyFragment extends Fragment implements OnClickListener {
private void showDialog() {
DialogFragment dialogFrag = MyDialogFragment.newInstance(this);
// Add this
dialogFrag.setTargetFragment(this, 0);
dialogFrag.show(getFragmentManager, null);
}
...
}
// then
public class MyialogFragment extends DialogFragment {
@Override
public void onAttach(Context context) {
super.onAttach(context);
// Then get it
Fragment fragment = getTargetFragment();
if (fragment instanceof OnClickListener) {
listener = (OnClickListener) fragment;
} else {
throw new RuntimeException("you must implement OnClickListener");
}
}
...
}
Ответ 12
Я решил это элегантным способом с помощью 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