7
votes

Écouteur DialogFragment qui persiste lors des modifications de configuration

Le scénario est le suivant, j'ai un ViewPager qui contient des fragments, chacun de ces fragments a une action qui nécessite une confirmation.

Je procède à la création d'un DialogFragment ciblant le fragment qui sait également gérer le résultat, mais le fragment peut être recréé avant que l'utilisateur ne confirme ou refuse le dialogue.

Je pourrais passer un lambda, ou une autre forme d'écouteur à la boîte de dialogue, qui serait alors appelée lorsque l'utilisateur confirme la boîte de dialogue, mais le problème est que si l'appareil est ensuite pivoté, le lambda est perdu, car il ne peut pas être persistant sur le bundle ...

J'ai essayé de rechercher des solutions existantes en ligne, telles que l ' exemple de bibliothèques material-dialogs , mais la plupart des cas semblent rejeter la boîte de dialogue lors de la rotation, mais cela semble également être une solution bâclée, car la boîte de dialogue peut faire partie d'un flux plus long, tel que

demander un achat -> annuler -> afficher la boîte de dialogue avec l'explication -> acheter à nouveau si l'utilisateur le souhaite

où l'état du flux serait perdu, si nous fermons simplement la boîte de dialogue lors de la rotation


0 commentaires

5 Réponses :


0
votes

Au lieu d'utiliser des rappels pour capturer une référence à votre objet cible, essayez LocalBroadcastManager ( docs ).

Les principaux avantages de cette méthode sont:

  1. Aucune dépendance supplémentaire pour votre projet, car LocalBroadcastManager fait partie à la fois de support-v4 et / ou d'AndroidX legacy-support-v4 , ce que vous déjà.

  2. Pas besoin de conserver de références.

En un mot:

  • Dans le DialogFragment au lieu d'appeler un rappel, vous envoyez un Intent avec un message via le LocalBroadcastManager , et
  • Dans votre fragment cible, au lieu de passer un rappel au DialogFragment, vous utilisez un BroadcastReceiver pour écouter les messages qui passent par LocalBroadcastManager .

Pour envoyer depuis DialogFragment:

private final BroadcastReceiver localReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        // Do whatever you need to do here
    }
};

@Override
protected void onStart() {
    super.onStart();

    final IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(MyDialogFragment.MY_ACTION);
    LocalBroadcastManager.getInstance(getContext())
        .registerReceiver(localReceiver, intentFilter);
}

@Override
protected void onStop() {
    super.onStop();
    LocalBroadcastManager.getInstance(this)
        .unregisterReceiver(localReceiver);
}

Et pour écouter les messages dans votre fragment cible:

public static final String MY_ACTION = "DO SOMETHING";

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
  final Button button = view.findViewById(R.id.accept);
    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent broadcastIntent = new Intent(MY_ACTION);
            LocalBroadcastManager.getInstance(getContext()).sendBroadcast(broadcastIntent);
            dismiss();
        }
    });
}


0 commentaires

0
votes

La meilleure façon de gérer les dialogues que j'ai trouvés est avec EventBus. Vous envoyez essentiellement des événements à partir de boîtes de dialogue et les interceptez dans Activités / Fragments.

Vous pouvez attribuer des ID aux boîtes de dialogue lors de l'instanciation et ajouter cet ID aux événements pour distinguer les événements de différentes boîtes de dialogue (même si les boîtes de dialogue sont du même type).

Vous pouvez voir comment ce schéma fonctionne et obtenir des idées supplémentaires en consultant le code ici . Vous pouvez également trouver cette classe d'aide que j'ai écrite utile (mais attention car ce code est très ancien; par exemple, je ne retiens plus les dialogues).

Pour compléter la réponse, je publierai quelques extraits ici. Notez que ces extraits utilisent déjà la nouvelle FragmentFactory, les boîtes de dialogue ont donc des arguments de constructeur. C'est un ajout relativement récent, donc votre code ne l'utilisera probablement pas.

Cela pourrait être une implémentation d'une boîte de dialogue qui affiche des informations et qui a un bouton. Vous voudriez savoir quand cette boîte de dialogue est fermée:

public class PromptDialog extends BaseDialog {

    public static final String ARG_TITLE = "ARG_TITLE";
    public static final String ARG_MESSAGE = "ARG_MESSAGE";
    public static final String ARG_POSITIVE_BUTTON_CAPTION = "ARG_POSITIVE_BUTTON_CAPTION";
    public static final String ARG_NEGATIVE_BUTTON_CAPTION = "ARG_NEGATIVE_BUTTON_CAPTION";

    private final EventBus mEventBus;

    private TextView mTxtTitle;
    private TextView mTxtMessage;
    private Button mBtnPositive;
    private Button mBtnNegative;

    public PromptDialog(EventBus eventBus) {
        mEventBus = eventBus;
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext());
        LayoutInflater inflater = LayoutInflater.from(getContext());
        View dialogView = inflater.inflate(R.layout.dialog_info_prompt, null);
        dialogBuilder.setView(dialogView);

        initSubViews(dialogView);

        populateSubViews();

        setCancelable(false);

        return dialogBuilder.create();
    }

    private void initSubViews(View rootView) {
        mTxtTitle = (TextView) rootView.findViewById(R.id.txt_dialog_title);
        mTxtMessage = (TextView) rootView.findViewById(R.id.txt_dialog_message);
        mBtnPositive = (Button) rootView.findViewById(R.id.btn_dialog_positive);
        mBtnNegative = (Button) rootView.findViewById(R.id.btn_dialog_negative);

        mBtnPositive.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
                mEventBus.post(new PromptDialogDismissedEvent(getDialogTag(), PromptDialogDismissedEvent.BUTTON_POSITIVE));
            }
        });

        mBtnNegative.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
                mEventBus.post(new PromptDialogDismissedEvent(getDialogTag(), PromptDialogDismissedEvent.BUTTON_NEGATIVE));
            }
        });
    }

    private void populateSubViews() {
        String title = getArguments().getString(ARG_TITLE);
        String message = getArguments().getString(ARG_MESSAGE);
        String positiveButtonCaption = getArguments().getString(ARG_POSITIVE_BUTTON_CAPTION);
        String negativeButtonCaption = getArguments().getString(ARG_NEGATIVE_BUTTON_CAPTION);

        mTxtTitle.setText(TextUtils.isEmpty(title) ? "" : title);
        mTxtMessage.setText(TextUtils.isEmpty(message) ? "" : message);
        mBtnPositive.setText(positiveButtonCaption);
        mBtnNegative.setText(negativeButtonCaption);
    }

    @Override
    public void onCancel(DialogInterface dialog) {
        dismiss();
        mEventBus.post(new PromptDialogDismissedEvent(getDialogTag(), PromptDialogDismissedEvent.BUTTON_NONE));
    }
}

Et cette boîte de dialogue offre à l'utilisateur le choix entre deux options:

public class InfoDialog extends BaseDialog {

    public static final String ARG_TITLE = "ARG_TITLE";
    public static final String ARG_MESSAGE = "ARG_MESSAGE";
    public static final String ARG_BUTTON_CAPTION = "ARG_POSITIVE_BUTTON_CAPTION";

    private final EventBus mEventBus;

    private TextView mTxtTitle;
    private TextView mTxtMessage;
    private Button mBtnPositive;

    public InfoDialog(EventBus eventBus) {
        mEventBus = eventBus;
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext());
        LayoutInflater inflater = LayoutInflater.from(getContext());
        View dialogView = inflater.inflate(R.layout.dialog_info_prompt, null);
        dialogBuilder.setView(dialogView);

        initSubViews(dialogView);

        populateSubViews();

        setCancelable(true);

        return dialogBuilder.create();
    }

    private void initSubViews(View rootView) {
        mTxtTitle = (TextView) rootView.findViewById(R.id.txt_dialog_title);
        mTxtMessage = (TextView) rootView.findViewById(R.id.txt_dialog_message);
        mBtnPositive = (Button) rootView.findViewById(R.id.btn_dialog_positive);

        // Hide "negative" button - it is used only in PromptDialog
        rootView.findViewById(R.id.btn_dialog_negative).setVisibility(View.GONE);

        mBtnPositive.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
            }
        });

    }

    private void populateSubViews() {
        String title = getArguments().getString(ARG_TITLE);
        String message = getArguments().getString(ARG_MESSAGE);
        String positiveButtonCaption = getArguments().getString(ARG_BUTTON_CAPTION);

        mTxtTitle.setText(TextUtils.isEmpty(title) ? "" : title);
        mTxtMessage.setText(TextUtils.isEmpty(message) ? "" : message);
        mBtnPositive.setText(positiveButtonCaption);
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);
        mEventBus.post(new InfoDialogDismissedEvent(getDialogTag()));
    }
}


0 commentaires

3
votes

Si vous passez un lambda / Listener anonyme, vous le perdrez après la rotation mais si vous faites en sorte que votre activité implémente votre auditeur et l'assigne dans la méthode onAttach (context) du fragment, il sera réaffecté après l'activité recréer.

open class BaseDialogFragment : DialogFragment() {
    var listener: FlowStepListener? = null

    override fun onAttach(context: Context) {
        super.onAttach(context)
        if (context is FlowStepListener) {
            listener = context
        } else {
            throw RuntimeException("$context must implement FlowStepListener")
        }
    }

    override fun onDetach() {
        super.onDetach()
        listener = null
    }
}
class ParentActivity: Activity(), FlowStepListener {
    override fun onFirstStepPassed() {
        //control your fragments here
    }
    override fun onSecondStepPassed() {
        //control your fragments here
    }
    override fun onThirdStepPassed() {
        //control your fragments here
    }
}
interface FlowStepListener {
    fun onFirstStepPassed()
    fun onSecondStepPassed()
    fun onThirdStepPassed()
}


0 commentaires

0
votes

Vous pouvez utiliser un ViewModel :

La classe ViewModel est conçue pour stocker et gérer les données liées à l'interface utilisateur en tenant compte du cycle de vie. La classe ViewModel permet aux données de survivre aux modifications de configuration telles que les rotations d'écran .

La documentation aborde également les fragments dans la section Partager des données entre les fragments .

... Ce problème commun peut être résolu en utilisant des objets ViewModel. Ces fragments peuvent partager un ViewModel en utilisant leur étendue d'activité pour gérer cette communication ...

La partie intéressante peut être:

Notez que les deux fragments récupèrent l'activité qui les contient. De cette façon, lorsque les fragments obtiennent chacun le ViewModelProvider, ils reçoivent la même instance SharedViewModel , qui est limitée à cette activité.

Voyez ci-dessous comment le viewModel survit à la rotation de l'écran. entrez la description de l'image ici


1 commentaires

Les ViewModels ne sont pas censés contenir un contexte. Si l'activité ou le fragment parent est l'écouteur, vous enfreignez cette règle.



0
votes

L'auditeur produira un couplage de code. Dans votre cas, pourquoi ne pas utiliser le bus d'événements. Le bus événementiel en interne fonctionne un peu comme les auditeurs, mais vous n'avez rien à gérer vous-même. Voici les étapes pour utiliser le bus d'événements. Créez un objet événement (il vaut mieux le garder propre par un objet)

implementation "org.greenrobot:eventbus:3.1.1"

Ensuite, postez votre événement

@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (!EventBus.getDefault().isRegistered(this)) {
            EventBus.getDefault().register(this);
        }

    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }

Et recevez-le dans votre Activité / Fragment

 @Subscribe
    public void onEvent(DialogDataEvent event) {
   //Do Some work here

    }

N'oubliez pas de vous inscrire et de désinscrire votre bus événementiel en classe de réception

EventBus.getDefault().post(new DialogDataEvent("data"));

Et pour MAMA Gradle: D

    public class DialogDataEvent {

    String someData;  

    public DialogDataEvent(String data){
    this.someData=data;

}

    }


0 commentaires