Обратный вызов фрагмента из 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
получить ссылку.
я следовал этим простым шагам, чтобы сделать это.
- создать интерфейс, как
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);
что это все сделано! просто убедитесь, что вы реализовали этот интерфейс в своем фрагменте.
на связь с другими фрагментами руководство говорит, что осколки должны общаться через связанную деятельность.
часто вы захотите, чтобы один фрагмент общался с другим, для пример изменения содержимого на основе события 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){
}
...
}
Я столкнулся с подобной проблемой. Решение, которое я нашел, было :
объявите интерфейс в вашем 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 в 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() {
}
});
}
}