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

вопрос: Как создать обратный вызов из DialogFragment в другой фрагмент. В моем случае активность должна быть полностью не осведомлена о 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: index 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


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

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


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

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

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

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

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


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

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


вы должны определить 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


Я получаю результат для фрагмента 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 в observer, чтобы избежать утечек памяти. Вот код:

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() {

            }
        });
    }
}