Reputation: 1334
I use DialogFragment (onCreateDialog)
and ViewModel for it. But, when I try to pass getViewLifecycleOwner()
to the LiveData::observe
method I get the error below:
java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null i.e., before onCreateView() or after onDestroyView().
Is it possible to use getViewLifecycleOwner()
inside a DialogFragment
?
Upvotes: 37
Views: 21868
Reputation: 1
Because DialogFragment
pops over the view of a parent fragment it can use its lifecycle owner. Thus code will look like this:
parentFragment?.viewLifecycleOwner?.let {
binding.lifecycleOwner = it
}
Upvotes: 0
Reputation: 19562
This is the official recommendation for DialogFragment
s:
Note: When subscribing to lifecycle-aware components such as
LiveData
, you should never useviewLifecycleOwner
as theLifecycleOwner
in aDialogFragment
that usesDialogs
. Instead, use theDialogFragment
itself, or if you're using Jetpack Navigation, use theNavBackStackEntry
.
So you can just observe things as normal, but instead of viewLifecycleOwner
you pass this
, or the current backstack entry (e.g. findNavController().currentBackStackEntry
). No need to override onCreateView
just to force a viewLifecycleOwner
to be created or anything!
Upvotes: 19
Reputation: 1323
My solution was a little bit wacky...
My component was using the getViewLifecycleOwnerLiveData() .... so:
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);
}
Because FragmentViewLifecycleOwner is package private ... that's the reason for the MyLifeCycleOwner class.
I was not gonna change my components because of some mismanagement on the android architecture...
Upvotes: 0
Reputation: 17556
this happens because the lifecycle of DialogFragment
is different from Fragment
; onCreateDialog
is called before onCreateView
, so viewLifecycleOwner
is unavailable... I worked around the issue by:
onCreateView
instead of onCreateDialog
viewLifecycleOwner
from within onCreateView
onCreateView
is put into a dialog for us by DialogFragment
...supplemental code:
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
}
...
}
the layout file used
<?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>
Upvotes: 7
Reputation: 6314
This happens because of how the DialogFragment is created.
If you use onCreateDialog()
than a slightly different lifecycle is used for this type of Fragment. The onCreateView()
will not be used, thus the viewLifecycleOwner
for this Fragment won't be initialized.
As a workaround for this, you can use the Fragment instance as the owner for the observer:
.observe(this, Observer {...}
. Although you will get a warning for using this
instead of the viewLifecycleOwner
.
Upvotes: 16
Reputation: 179
Your case is slightly different but I think the concept is kind of the same. Just use this.getActivity()
in your Dialog Class and pass it as LifeCycleOwner
. I had the same problem because I used LiveData
and Retrofit
and LiveData
needs a reference. The DialogFragment
sets its LifeCycleOwner
at some point but it is not at any of the methods mentioned above. By using the getActivity()
you can use your observer as early as in onCreateDialog method. Here is some portion of my code that at first caused some issue when I tried to pass a null referenced this.getViewLifecycleOwner()
instead of the activity.
@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;
}
Upvotes: 8