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

Вопрос: Как создать обратный вызов из диалога 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