Question: Comment créer un rappel depuis un DialogFragment vers un autre fragment? Dans mon cas, l'activité impliquée doit être complètement ignorante du DialogFragment.
Considère que j'ai
public class MyFragment extends Fragment implements OnClickListener
Puis à un moment donné je pourrais faire
DialogFragment dialogFrag = MyDialogFragment.newInstance(this);
dialogFrag.show(getFragmentManager, null);
Où se trouve MyDialogFragment
protected OnClickListener listener;
public static DialogFragment newInstance(OnClickListener listener) {
DialogFragment fragment = new DialogFragment();
fragment.listener = listener;
return fragment;
}
Mais rien ne garantit que l'auditeur sera présent si le DialogFragment s'interrompt et reprend son cycle de vie. Les seules garanties contenues dans un fragment sont celles transmises via un bundle via setArguments et getArguments.
Il existe un moyen de référencer l'activité si ce doit être l'auditeur:
public Dialog onCreateDialog(Bundle bundle) {
OnClickListener listener = (OnClickListener) getActivity();
....
return new AlertDialog.Builder(getActivity())
........
.setAdapter(adapter, listener)
.create();
}
Mais je ne veux pas que l'activité soit à l'écoute des événements, j'ai besoin d'un fragment. En réalité, il pourrait s'agir de n'importe quel objet Java implémentant OnClickListener.
Prenons l'exemple concret d'un fragment qui présente un AlertDialog via DialogFragment. Il a des boutons Oui/Non. Comment puis-je renvoyer ces pressions sur le fragment qui l'a créé?
L'activité impliquée ignore complètement le DialogFragment.
Classe de fragment:
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;
}
}
}
}
Classe 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();
}
}
La solution TargetFragment ne semble pas être la meilleure option pour les fragments de dialogue, car elle peut créer IllegalStateException
après que l'application ait été détruite et recréée. Dans ce cas, FragmentManager
n'a pas pu trouver le fragment cible et vous obtiendrez une IllegalStateException
avec un message comme celui-ci:
"Le fragment n'existe plus pour la clé Android: target_state: index 1"
Il semble que Fragment#setTargetFragment()
ne soit pas destiné à la communication entre un fragment enfant et parent, mais plutôt à une communication entre des fragments frères et sœurs.
La méthode alternative consiste donc à créer des fragments de dialogue comme celui-ci en utilisant la variable ChildFragmentManager
du fragment parent, plutôt que les activités FragmentManager
:
dialogFragment.show(ParentFragment.this.getChildFragmentManager(), "dialog_fragment");
Et en utilisant une interface, dans la méthode onCreate
de la DialogFragment
, vous pouvez obtenir le fragment parent:
@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");
}
}
La seule chose qui reste à faire est d'appeler votre méthode de rappel après ces étapes.
Pour plus d'informations sur le problème, vous pouvez consulter le lien: https://code.google.com/p/Android/issues/detail?id=54520
Peut-être un peu tard, mais peut aider d'autres personnes avec la même question, comme je l'ai fait.
Vous pouvez utiliser setTargetFragment
sur Dialog
avant d'afficher et dans la boîte de dialogue, vous pouvez appeler getTargetFragment
pour obtenir la référence.
J'ai suivi ces étapes simples pour faire ce genre de choses.
DialogFragmentCallbackInterface
avec une méthode telle que callBackMethod(Object data)
. Ce que vous appelez pour transmettre des données.DialogFragmentCallbackInterface
dans votre fragment comme MyFragment implements DialogFragmentCallbackInterface
Au moment de la création de DialogFragment
, définissez votre fragment appelant MyFragment
en tant que fragment cible ayant créé DialogFragment
, utilisez myDialogFragment.setTargetFragment(this, 0)
check setTargetFragment (fragment de fragment, int requestCode)
MyDialogFragment dialogFrag = new MyDialogFragment();
dialogFrag.setTargetFragment(this, 1);
Obtenez votre objet fragment cible dans votre DialogFragment
en appelant getTargetFragment()
et transcrivez-le en DialogFragmentCallbackInterface
.Maintenant, vous pouvez utiliser cette interface pour envoyer des données à votre fragment.
DialogFragmentCallbackInterface callback =
(DialogFragmentCallbackInterface) getTargetFragment();
callback.callBackMethod(Object data);
C'est tout fait! assurez-vous simplement que vous avez implémenté cette interface dans votre fragment.
Le guide Communication avec d'autres fragments / indique que les fragments doivent communiquer par le biais de l'activité associée.
Souvent, vous voudrez qu'un fragment communique avec un autre, par exemple exemple pour changer le contenu basé sur un événement utilisateur. Tout La communication fragment à fragment se fait par le biais du .__ associé. Activité. Deux fragments ne doivent jamais communiquer directement.
Vous devez définir un interface
dans votre classe de fragment et implémenter cette interface dans son activité parente. Les détails sont décrits ici http://developer.Android.com/guide/components/fragments.html#EventCallbacks . Le code ressemblerait à:
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");
}
}
}
Activité:
public class MyActivity extends Activity implements OnArticleSelectedListener{
...
@Override
public void onArticleSelected(Uri articleUri){
}
...
}
Je faisais face à un problème similaire. La solution que j'ai trouvée était:
Déclarez une interface dans votre DialogFragment exactement comme James McCracken l'a expliqué ci-dessus.
Implémentez l'interface dans votre activité (pas fragmenté! Ce n'est pas une bonne pratique).
À partir de la méthode de rappel de votre activité, appelez une fonction publique requise dans votre fragment, qui effectue le travail que vous souhaitez effectuer.
Ainsi, cela devient un processus en deux étapes: DialogFragment -> Activité puis Activité -> Fragment.
La manière correcte de définir un auditeur sur un fragment consiste à le définir lorsqu'il est attaché . Le problème que j'ai eu est que onAttachFragment () n'a jamais été appelé. Après quelques recherches, j'ai réalisé que j'avais utilisé getFragmentManager au lieu de getChildFragmentManager
Voici comment je le fais:
MyDialogFragment dialogFragment = MyDialogFragment.newInstance("title", "body");
dialogFragment.show(getChildFragmentManager(), "SOME_DIALOG");
Attachez-le à 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() {
}
});
}
}
Mis à jour:
J'ai créé une bibliothèque basée sur mon code Gist qui génère ces castings en utilisant @CallbackFragment
et @Callback
.
https://github.com/zeroarst/callbackfragment .
Et l'exemple vous donne l'exemple qui envoie un rappel d'un fragment à un autre fragment.
Ancienne réponse:
J'ai créé une BaseCallbackFragment
et une annotation @FragmentCallback
. Il étend actuellement Fragment
, vous pouvez le changer en DialogFragment
et fonctionnera. Il vérifie les implémentations dans l'ordre suivant: getTargetFragment ()> getParentFragment ()> context (activity).
Il vous suffit ensuite de l'étendre, de déclarer vos interfaces dans votre fragment et de lui donner l'annotation. Le fragment de base fera le reste. L'annotation comporte également un paramètre mandatory
qui vous permet de déterminer si vous souhaitez forcer le fragment à implémenter le rappel.
public class EchoFragment extends BaseCallbackFragment {
private FragmentInteractionListener mListener;
@FragmentCallback
public interface FragmentInteractionListener {
void onEcho(EchoFragment fragment, String echo);
}
}
https://Gist.github.com/zeroarst/3b3f32092d58698a4568cdb0919c9a93
J'obtiens le résultat de Fragment DashboardLiveWall (fragment appelant) à partir de Fragment LiveWallFilterFrter (fragment de réception).
LiveWallFilterFragment filterFragment = LiveWallFilterFragment.newInstance(DashboardLiveWall.this ,"");
getActivity().getSupportFragmentManager().beginTransaction().
add(R.id.frame_container, filterFragment).addToBackStack("").commit();
où
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 retourne à l'appel d'un fragment comme
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 {
}
}
}
}
}
Selon la documentation officielle:
Cible facultative pour ce fragment. Cela peut être utilisé, par exemple, si ce fragment est démarré par un autre et que, lorsque cela est fait, veut rendre un résultat au premier. La cible définie ici est conservée d'une instance à l'autre via FragmentManager # putFragment.
Renvoie le fragment cible défini par setTargetFragment (Fragment, int).
Donc, vous pouvez faire ceci:
// 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");
}
}
...
}
Je l'ai résolu de manière élégante avec RxAndroid. Recevez un observateur dans le constructeur de DialogFragment et abonnez-vous à observable et envoyez la valeur lorsque le rappel est appelé. Ensuite, dans votre fragment, créez une classe interne de Observer, créez une instance et transmettez-la au constructeur de DialogFragment. J'ai utilisé WeakReference dans l'observateur pour éviter les fuites de mémoire. Voici le code:
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() {
}
}
Vous pouvez voir le code source ici: https://github.com/andresuarezz26/carpoolapp