material-components-android icon indicating copy to clipboard operation
material-components-android copied to clipboard

MaterialDatePicker callbacks lost on orientation change

Open Nailik opened this issue 5 years ago • 9 comments
trafficstars

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

Nailik avatar Sep 04 '20 09:09 Nailik

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 avatar Sep 08 '20 15:09 ymarian

@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.

Nailik avatar Sep 08 '20 21:09 Nailik

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);
            });}
}

VitKap avatar Nov 23 '21 11:11 VitKap

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
  }
}

Rajarml avatar Nov 23 '22 15:11 Rajarml

I would recommend to simply switch to Jetpack compose.

Nailik avatar Nov 23 '22 21:11 Nailik

@Nailik does it resolve the lost listeners issue after a config change like device rotation?

Rajarml avatar Nov 24 '22 10:11 Rajarml

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.

VyacheslavMartynenko avatar Jan 12 '23 09:01 VyacheslavMartynenko

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.

vullnetlimani avatar May 01 '24 22:05 vullnetlimani

Let me take another look at this

paulfthomas avatar May 02 '24 12:05 paulfthomas

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.

image

vullnetlimani avatar Jul 11 '24 23:07 vullnetlimani

@vullnetlimani the fix didn't make it to the alpha04 release, it will be part of the next one, probably in a couples weeks.

paulfthomas avatar Jul 15 '24 15:07 paulfthomas

ไม่สามารถจัดระบบให้ผู้ฟังเพื่อประโยชน์ของพวกมันได้

เราจะอธิบายใหม่เมื่อมีการกล่าวถึงคุณในการค้นหาส่วนต่างๆ ของชุดแท็กเมื่อเรียก show

เพียงรีเซ็ตผู้ฟังใน onResume หรือเมื่อรับสัญญาณ:

MaterialDatePicker datepicker  = (MaterialDatePicker) supportFragmentManager.findFragmentByTag("tag");
// reset listeners

Benz19933 avatar Jul 20 '24 14:07 Benz19933