Reputation: 1039
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!
Upvotes: 0
Views: 2620
Reputation: 4694
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.
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:
private lateinit var modal: AddReminderFragment
variable from MainActivity
;ITimePipupCallback
from MainActivity
;onAttach(context: Context)
from TimePopupFragment
;fun setCallback(callback: ITimePipupCallback): TimePopupFragment
in TimePopupFragment
;TimePopupFragment
from AddReminderFragment
passed in a callback ITimePipupCallback
instance. As if it was a callback to a button click of an AlertDialog
;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
.
Upvotes: 2
Reputation: 45
Make sure that you are using fragment's view model. You should not clear the view model by posting values to live data postValue(T value)
as it is unnecessary.
Use the following view model,
class MyViewModel : ViewModel() {
private val users: MutableLiveData<List<User>> by lazy {
MutableLiveData().also {
loadUsers()
}
}
fun getUsers(): LiveData<List<User>> {
return users
}
private fun loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
In DialogFragment, initialize the view model by
ViewModelProviders.of(<FRAGMENTT>).get(MyViewModel.class);
If you are using ktx then initialize the view model by
// Get a reference to the ViewModel scoped to this Fragment
val viewModel by viewModels<MyViewModel>()
Upvotes: 0