23
votes

getViewLifecycleOwner () dans DialogFragment entraîne un crash

J'utilise DialogFragment (onCreateDialog) et ViewModel pour cela. Mais, lorsque j'essaie de transmettre getViewLifecycleOwner() à la méthode LiveData::observe , j'obtiens l'erreur ci-dessous:

java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null i.e., before onCreateView() or after onDestroyView().

Est-il possible d'utiliser getViewLifecycleOwner() dans un DialogFragment ?


2 commentaires

Dans onCreateDialog Dialog crée pas encore créé. Essayez onViewCreated() . Je n'ai moi-même pas beaucoup utilisé LifecycleOwner .


onViewCreated n'est pas appelé dans DialogFragment


4 Réponses :


7
votes

Votre cas est légèrement différent, mais je pense que le concept est un peu le même. Utilisez simplement this.getActivity() dans votre classe de dialogue et transmettez-le en tant que LifeCycleOwner . J'ai eu le même problème car j'ai utilisé LiveData et Retrofit et LiveData besoin d'une référence. Le DialogFragment définit son LifeCycleOwner à un moment donné, mais il ne correspond à aucune des méthodes mentionnées ci-dessus. En utilisant getActivity() vous pouvez utiliser votre observateur dès la méthode onCreateDialog. Voici une partie de mon code qui a d'abord causé un problème lorsque j'ai essayé de passer un null référencé this.getViewLifecycleOwner() au lieu de l'activité.

@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
       FragmentActivity activity = this.getActivity();
       binding = DialogSelectIssuesBinding.inflate(LayoutInflater.from(getContext()));

       RetroRepository.
            getDefault().
            getAllIssues().
            observe(this.getActivity(), listLiveDataResponse -> {
                //ToDo Check for errors and Bind the data here 
            });


       AlertDialog alertDialog = new AlertDialog.Builder(activity)
                            .setView(binding.getRoot())
                            .setTitle("Please select issues from the list below:")
                            .setNegativeButton("CANCEL", null)
                            .setPositiveButton("ADD", null)
                            .create();
       alertDialog.setCanceledOnTouchOutside(false);
       return alertDialog;
}


1 commentaires

Et si le fragment est détruit puis recréé, mais l'activité ne l'est pas? Les anciens observateurs font toujours référence à l'ancien fragment, et vous vous êtes fait une fuite de mémoire! Le point de vue viewLifecycleOwner est de supprimer les observateurs lorsque le fragment est détruit.



7
votes

Cela se produit en raison de la façon dont le DialogFragment est créé. Si vous utilisez onCreateDialog() un cycle de vie légèrement différent est utilisé pour ce type de fragment. onCreateView() ne sera pas utilisé, donc le viewLifecycleOwner pour ce fragment ne sera pas initialisé.

: En tant que solution de contournement pour cela, vous pouvez utiliser l'instance Fragment en tant que propriétaire de l'observateur .observe(this, Observer {...} Bien que vous obtiendrez un message d' avertissement pour l' utilisation. this lieu du viewLifecycleOwner .


2 commentaires

Je ne semble recevoir aucun avertissement en utilisant this (MyDialogFragment). Cette approche est-elle préférable à l'utilisation de getActivity() ou de requireActivity() ?


@ ban-géoingénierie Tant que vous ne show la DialogFragment plusieurs fois avec la même instance de fragment. Si vous le faites, les observateurs seront inscrits une deuxième fois et vous pourriez avoir des problèmes étranges en raison d'une double observation. En effet , en utilisant this que le propriétaire du cycle de vie, les observateurs ne sont supprimés lorsque onDestroy est appelé, et il n'est pas appelé en rejetant simplement la boîte de dialogue.



2
votes

cela se produit car le cycle de vie de DialogFragment est différent de celui de Fragment ; onCreateDialog est appelé avant onCreateView , donc viewLifecycleOwner n'est pas disponible ... J'ai contourné le problème en:

  • onCreateView au lieu de onCreateDialog
    • peut accéder à viewLifecycleOwner depuis onCreateView
    • la vue renvoyée par onCreateView est mise dans une boîte de dialogue pour nous par DialogFragment ...
    • vous devrez créer vos propres boutons et titres dans la boîte de dialogue ...

code supplémentaire:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:minHeight="?attr/actionBarSize"
            tools:title="Title" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerview"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

        <LinearLayout
            style="?buttonBarStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:clickable="false"
            android:gravity="end"
            android:orientation="horizontal"
            android:padding="@dimen/min_touch_target_spacing_half">

            <Button
                android:id="@+id/button_reject"
                style="?buttonBarButtonStyle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/min_touch_target_spacing_half"
                android:text="@android:string/cancel" />

            <Button
                android:id="@+id/button_affirm"
                style="?buttonBarButtonStyle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/min_touch_target_spacing_half"
                android:text="@android:string/ok" />

        </LinearLayout>

    </LinearLayout>

</layout>

le fichier de mise en page utilisé

class TextInputDialogFragment : DialogFragment() {

    ...

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View? {
        val viewBinding = FragmentDialogTextInputBinding.inflate(layoutInflater, container, false)

        val titleText = params.title.localize(requireContext())
        viewBinding.toolbar.isVisible = titleText.isNotBlank()
        if (titleText.isNotBlank()) {
            viewBinding.toolbar.title = titleText
        }

        viewBinding.recyclerview.adapter = ListItemAdapter(
            viewLifecycleOwner, requireContext().app.nowFactory, viewModel.fields
        )

        viewBinding.buttonAffirm.setOnClickListener {
            listener.onOkPressed(viewModel.userInputtedText.value)
            dismiss()
        }

        viewBinding.buttonReject.setOnClickListener {
            dismiss()
        }

        viewModel.enablePositiveButton.observe(viewLifecycleOwner) { isEnabled ->
            viewBinding.buttonAffirm.isEnabled = isEnabled
        }

        return viewBinding.root
    }

    ...
}


0 commentaires

0
votes

Ma solution était un peu farfelue ...

Mon composant utilisait le getViewLifecycleOwnerLiveData () .... donc:

private final MyLifeCycleOwner owner = new MyLifeCycleOwner();

private final MutableLiveData<LifecycleOwner> result = new MutableLiveData<>();

@NonNull
@Override
public LiveData<LifecycleOwner> getViewLifecycleOwnerLiveData() {
    return result;
}

@Override
public void onDestroyView() {
    super.onDestroyView();
    owner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
    result.setValue(null);
}

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    result.setValue(owner);
    owner.getLifecycle();
    owner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
    return super.onCreateView(inflater, container, savedInstanceState);
}

Parce que FragmentViewLifecycleOwner est un package privé ... c'est la raison de la classe MyLifeCycleOwner.

Je n'allais pas changer mes composants à cause d'une mauvaise gestion de l'architecture android ...


0 commentaires