Search code examples
androidkotlinandroid-livedataandroid-viewmodel

Android: Clear DialogFragment ViewModel


I am creating a dialog extending BottomSheetDialogFragment. Inside this dialog I have a ViewModel to hold the state of the fragment (it has some inputs inside). Some of the properties of the ViewModel are LiveData. I've noticed that when dismissing the dialog (either calling dismiss() or tapping outside the dialog) and reopening it, the values of the LiveData properties persist.

So as a solution, I thought to override the onDismiss() event to manually clear the data inside the ViewModel:

 override fun onDismiss(dialog: DialogInterface) {
//        Clear the viewmodel
        viewModel.clear()
        viewModelStore.clear()

        super.onDismiss(dialog)
//        Unsubscribe from observer
        viewModel.getContact().removeObservers(viewLifecycleOwner)

    }

viewModel.clear() is a function in my ViewModel that resets the data inside to default values.

Apparently, this wasn't enough either, since even after resetting the data inside the ViewModel before the dismissal, when reopening the dialog, the ViewModel hold the data that it held before calling clear()

Since the properties that are not LiveData do reset, I believe there is a specific way of reseting LiveData value, but I have no idea how to do it. Can anyone help me out? Thanks in advance!


Solution

  • Adrian kindly provided me with a sample of his project. This answer is based on the results of debugging the sample project.

    The problem causing the issue you have is actually located in your MainActivity class. You instantiate AddReminderFragment in onCreate method of your activity and when FAB is pressed you display the instance of this fragment.

    Everything seems good, except - you always present the same instance of AddReminderFragment. It means when you present this fragment for the first time you get brand new AddReminderViewModel that is lazy loaded using by viewModels().

    Initially, I thought the lazy loading by viewModels() was causing this issue because it uses the fragment itself as the view model store and it caches created view model in case you will want to reuse it later. It was not the case.

    How to fix the issue?

    Never store references to activities and fragments or avoid as much as you can and be careful when you have any. It can potentially lead to a memory leak.

    Shortly described what I did:

    1. Removed private lateinit var modal: AddReminderFragment variable from MainActivity;
    2. Removed ITimePipupCallback from MainActivity;
    3. Removed onAttach(context: Context) from TimePopupFragment;
    4. Implemented fun setCallback(callback: ITimePipupCallback): TimePopupFragment in TimePopupFragment;
    5. Before showing an instance of TimePopupFragment from AddReminderFragment passed in a callback ITimePipupCallback instance. As if it was a callback to a button click of an AlertDialog;
    6. Removed onTimePicked and onDayPicked methods from AddReminderFragment as they are no longer needed.

    Updated MainActivity:

    class MainActivity : AppCompatActivity() {
    
        companion object {
            const val AddReminderModalTag = "add_reminder_modal"
        }
    
        private lateinit var binding: MainActivityBinding
        
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = MainActivityBinding.inflate(layoutInflater)
            binding.showModalHandler = this
            setContentView(binding.root)
        }
    
        fun showAddReminderModal(view: View) {
            AddReminderFragment.newInstance().show(supportFragmentManager, AddReminderModalTag)
        }
    }
    

    Updates made to the AddReminderFragment:

    class AddReminderFragment : BottomSheetDialogFragment() {
        ...
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            ...
            
            //Bind the add time button
            binding.addTimeButton.setOnClickListener {
                TimePopupFragment
                    .newInstance(viewModel.getReminderType().value!!)
                    .setCallback(object : ITimePipupCallback {
                        override fun onTimePicked(hour: Int, minute: Int) {
                            viewModel.addTimeToRemind(hour, minute)
                        }
    
                        override fun onDayPicked(day: Int) {
                            viewModel.addTimeToRemind(day)
                        }
                    })
                    .show(
                        parentFragmentManager,
                        TIME_PICKER_TAG
                    )
            }
    
            ...
    
            return binding.root
        }
    
        ...
    }
    

    Updates made to TimePopupFragment:

    class TimePopupFragment : DialogFragment() {
        ...
        private lateinit var callback: ITimePipupCallback
        
        // Removed `onAttach` method
        fun setCallback(callback: ITimePipupCallback): TimePopupFragment {
            this.callback = callback
            return this
        }
    
        ...
    
    }
    

    You can safely remove override fun onDismiss(dialog: DialogInterface) from AddReminderFragment.