rmirabelle
rmirabelle

Reputation: 6444

ViewModel not cleared on DialogFragment onDestroy

Lifecycle v 2.3.1

Short Story: ViewModel is not cleared when DialogFragment onDestroy is called. That's it.

In my MainActivity, I instantiate and show a DialogFragment on button press:

private fun showConfirmDialog(message: String) {
    MyConfirmDialog.getInstance(message)
        .show(supportFragmentManager, MyConfirmDialog.TAG)
}

my dialog fragment:

class MyConfirmDialog: DialogFragment() {
    companion object {
        val TAG:String = MyConfirmDialog::class.java.simpleName
        const val ARGS_MESSAGE = "message"
    

        fun getInstance(message:String): MyConfirmDialog {
            val dialog = MyConfirmDialog()
            dialog.message = message
            val b = Bundle()
            b.putString(ARGS_MESSAGE, message)
            dialog.arguments = b
            return dialog
        }
    }

    private lateinit var message:String
    private val vm: MyConfirmDialogViewModel by activityViewModels()
    private lateinit var binding: MyConfirmDialogBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        message = requireArguments().getString(ARGS_MESSAGE, "")
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        binding = MyConfirmDialogBinding.inflate(inflater, container, false)
        with(binding) {
            btnDone.setOnClickListener {
                vm.doTheThing(message)
            }
            btnCancel.setOnClickListener {
                dismiss()
            }
        }
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        /**
         * Observer immediately fires on second fresh instance of this dialog.
         * regardless of whether observed with this or viewLifecycleOwner. Thus before 
         * it can even be shown, this second unique dialog is immediately dismissed. 
         * The observer shouldn't receive an update until after btnDone is clicked.
         */
        vm.doTheThing.observe(viewLifecycleOwner, {
            Log.e(TAG, "observed doTheThing")
            dismiss()
        })
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.e(TAG, "onDestroy")
        //VM NOT CLEARED
    }
}

I call showConfirmDialog a second time from my MainActivity, a second dialog is instantiated, but the VM is still happily sitting resident and NOT cleared, despite the first dialog confirming onDestroy having been called.

Thus is appears I can never use a DialogFragment with a ViewModel, if said dialog will be instantiated more than once.

This feels like a bug in Android lifecycle 2.3.1

Upvotes: 0

Views: 477

Answers (1)

rmirabelle
rmirabelle

Reputation: 6444

I think I found my own answer. I was, in fact, instantiating the VM like this:

private val vm: MyConfirmDialogViewModel by activityViewModels()

Had to change to:

private val vm: MyConfirmDialogViewModel by viewModels()

Apparently, the choice of delegate is absolutely critical. Using the first delegate (by activityViewModels()) was automatically scoping the VM lifetime to the lifetime of the Activity instead of the Dialog. Thus even when the dialog was destroyed the VM was not cleared.

I thought scoping the VM's lifecycle was handled when registering the observer (e.g. observe(this, {}) vs. observe(viewLifecycleOwner, {})), but apparently the choice of these two targets has only to do with whether you intend to override onCreateDialog. To me, it's a confusing mess.

It would certainly be a better world if I didn't have to know absolutely everything about lifecycles and all their intricacies just to use the SDK properly.

Upvotes: 2

Related Questions