material-components-android
material-components-android copied to clipboard
MaterialDatePicker callbacks lost on orientation change
Hi, due to Androids "weird" handling of rotation changes the callbacks of the MaterialDatePicker are "lost" after orientation change.
Because the MaterialDatePicker ist final there is no way to extend it and override it and handle the click in my own code. A good way would be to change this:
confirmButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
for (MaterialPickerOnPositiveButtonClickListener<? super S> listener :
onPositiveButtonClickListeners) {
listener.onPositiveButtonClick(getSelection());
}
dismiss();
}
});
to sth like
confirmButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
onOkClick();
}
});
and add a function
public void onOkClick(){
for (MaterialPickerOnPositiveButtonClickListener<? super S> listener :
onPositiveButtonClickListeners) {
listener.onPositiveButtonClick(getSelection());
}
dismiss();
}
And make MaterialDatePicker NOT final this way i could extend it and:
@Override
public void onOkClick(){
//save new date to my database
super.onOkClick();
}
I'm pretty sure that's a much more elegant solution than either
- disable orientation changes while dialog is opened
- check in my activity after orientation change if the dialog is opened and reattach my callback
We cannot serialize the listeners to retain them.
Since the dialog is going to be recreated on a config change, you can find the fragment by the tag set when calling show.
Just reset the listeners in onResume or when appropriate:
MaterialDatePicker datepicker = (MaterialDatePicker) supportFragmentManager.findFragmentByTag("tag");
// reset listeners
@ymarian I'm not sure if you did understand my solution.
I know that you can't serialize the listeners. That's the reason why i want to extend the MaterialDatePicker and not use a listener but do my database stuff right in my created class. But MaterialDatePicker is final (for what reason?)... so i can't do this.
Doing a lot such searches with findFragmentByTag is not a nice thing because i have to do it in every Activity that may sometimes show the Dialog or i have to do it in AppCompatDelegate - that's not a nice solution either.
Just set the listener in OnAttachFragment or create an OnFragmentAttachListener and after rotation the listener will be OK.
@Override
public void onAttachFragment(@NonNull Fragment childFragment) {
super.onAttachFragment(childFragment);
if (childFragment instanceof MaterialDatePicker) {
((MaterialDatePicker<?>) childFragment).addOnPositiveButtonClickListener(selection -> {
viewModel.setSelection((long) selection);
});}
}
onAttachFragment is now deprecated.
It is a bit more complicated now:
fun Fragment.addTimePickerPositiveListenerIfNeeded() {
val tag = this.tag
if (this is MaterialTimePicker && tag != null) {
clearOnPositiveButtonClickListeners()
addOnPositiveButtonClickListener {
setFragmentResult(
requestKey = tag,
result = bundleOf(TIME_ARG to LocalTime.of(hour, minute))
)
}
}
}
(We use ThreetenBP for LocalTime but any parcelable/serializable will do it)
In the onViewCreated():
childFragmentManager.addFragmentOnAttachListener { _, fragment ->
fragment.addTimePickerPositiveListenerIfNeeded()
}
But it is not enough because it doesn't cover already shown/existing MaterialTimePicker !
// For each tag:
childFragmentManager.findFragmentByTag(tag)?.addTimePickerPositiveListenerIfNeeded()
childFragmentManager.setFragmentResultListener(tag, this) { _, bundle ->
val localTime = bundle.getSerializable(TIME_ARG) as? LocalTime
if (localTime != null) {
// use localTime here
}
}
I would recommend to simply switch to Jetpack compose.
@Nailik does it resolve the lost listeners issue after a config change like device rotation?
I would recommend to simply switch to Jetpack compose.
It's not the best solution to this particular problem. Unfortunately, switching to Jetpack compose is not easy when we talk about a big project. This library supports fragments and the material date picker is a DialogFragment itself...
For this particular problem, there is a case that is valid for general AlertDialog. Just give us an opportunity to extend this class. Then we can send the result via modern navigation/communication patterns and we are not forced to implement extra steps for state restoration.
All modern Google libraries try to follow the new development ideas that will help to limit state restoration handling. I strongly believe that MaterialComponents should be on that list also.
It's disappointing to see that this bug was closed without addressing the underlying issue. This oversight leads to significant inconvenience, as the problem can manifest in various scenarios, such as when changing the device orientation or switching themes.
Currently, I'm resorting to a workaround by setting datePicker.retainInstance = true, despite its deprecated status. While this workaround functions across Android versions from 6 to 14, it's not a sustainable or recommended approach.
Google needs to take concrete action to resolve this issue properly. Relying on deprecated fixes isn't the right path forward, and users shouldn't have to deal with these workarounds to make their apps work reliably. The problem is more profound than just one occurrence and could affect many developers and users alike. It's crucial to prioritize a comprehensive solution that addresses this issue from the root.
Let me take another look at this
Hey there,
I noticed that this issue has been fixed, but it doesn't seem to be reflected in the library. As you can see, I'm using the latest version (1.13.0-alpha04). However, when I open MaterialDatePicker, I still see the old class. I saw that this class is no longer final, but as shown in the photo, it is still final, and the changes in the click listener are still from the old version.
Even I am using the class from the new updated version.
@vullnetlimani the fix didn't make it to the alpha04 release, it will be part of the next one, probably in a couples weeks.
ไม่สามารถจัดระบบให้ผู้ฟังเพื่อประโยชน์ของพวกมันได้
เราจะอธิบายใหม่เมื่อมีการกล่าวถึงคุณในการค้นหาส่วนต่างๆ ของชุดแท็กเมื่อเรียก show
เพียงรีเซ็ตผู้ฟังใน onResume หรือเมื่อรับสัญญาณ:
MaterialDatePicker datepicker = (MaterialDatePicker) supportFragmentManager.findFragmentByTag("tag"); // reset listeners