Search code examples
androidtimepicker

Wrong behaviour of FragmentDialog with TimePicker


I am creating a dialog with custom view. One of the component in the view is a TimePicker. When doing that, the OK+Cancel buttons of the TimePicker take control and the FragmentDialog default button are disable.

This cause a problem since depending on user data I show/hide the TimePicker view and when it is hidden it also hide the OK+Cancel buttons.

It is important to have a custom view, so the user will be able to select all relevant data in a single view.

Edit - Add image that shows the problem

enter image description here

layout.xml

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:minHeight="1000dp"
            android:paddingLeft="5dp"
            android:paddingRight="5dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_vertical">

            <TextView
                android:layout_width="0px"
                android:layout_height="wrap_content"
                android:layout_weight="50"
                android:text="@string/automatic_backup_type"
                android:textColor="@color/dialog_text_color"/>

            <!-- depending on the selected value here the timePart layout is showed or hide -->
            <Spinner
                android:id="@+id/automaticBackupType"
                android:layout_width="0px"
                android:layout_height="wrap_content"
                android:layout_weight="50"
                android:background="@drawable/dialog_spinner_background"
                android:prompt="@string/automatic_backup_type"
                android:textColor="@color/dialog_text_color"
                android:spinnerMode="dropdown"/>
        </LinearLayout>

<!-- Some more controls here that are not relevant -->

        <LinearLayout
            android:id="@+id/timePart"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:gravity="center_horizontal">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/automatic_backup_time"
                android:textColor="@color/dialog_text_color"/>

            <TimePicker
                android:id="@+id/time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
        </LinearLayout>
    </LinearLayout>
</ScrollView>

To show the dialog I use the code:

new AutomaticBackupConfigurationAlert().show(getSupportFragmentManager(), "TAG");

The AutomaticBackupConfigurationAlert code:

public static class AutomaticBackupConfigurationAlert extends DialogFragment {

    private Spinner    automaticBackupType;
    private View       timePart;
    private TimePicker time;

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        AutomaticBackupType type;
        Calendar time;

        if (savedInstanceState != null) {
            type = (AutomaticBackupType)savedInstanceState.getSerializable(KEY_TYPE);
            time = (Calendar)savedInstanceState.getSerializable(KEY_TIME);
        } else {
            type = SettingsHelper.getAutomaticBackupType();
            time = SettingsHelper.getAutomaticBackupTime();
        }

        LayoutInflater inflater = LayoutInflater.from(getActivity());
        @SuppressLint("InflateParams")
        View view = inflater.inflate(R.layout.dialog_automatic_backup, null);

        this.automaticBackupType = view.findViewById(R.id.automaticBackupType);
        this.timePart = view.findViewById(R.id.timePart); // When this is hide, no buttons for OK+Cancel
        this.time = view.findViewById(R.id.time);

        updateVisibility(type);

        {
            // backup type
            ArrayAdapter<AutomaticBackupType> adapter = new ArrayAdapter<>(getActivity(), R.layout.dialog_spinner_item, AutomaticBackupType.values());
            adapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
            automaticBackupType.setAdapter(adapter);
            automaticBackupType.setSelection(type.ordinal());
        }
        {
            // time
            this.time.setIs24HourView(DateFormat.is24HourFormat(getActivity()));
            this.time.setCurrentHour(time.get(Calendar.HOUR_OF_DAY));
            this.time.setCurrentMinute(time.get(Calendar.MINUTE));
        }

        automaticBackupType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                AutomaticBackupType tmpAutomaticBackupType = (AutomaticBackupType)automaticBackupType.getSelectedItem();
                updateVisibility(tmpAutomaticBackupType);
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {

            }
        });

        return new AlertDialog.Builder(getActivity())
                .setTitle(R.string.automatic_backup_configuration_title)
                .setView(view)
                .setPositiveButton(R.string.btn_save, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // Do nothing here because we override this button later to change the close behaviour.
                        // However, we still need this because on older versions of Android unless we
                        // pass a handler the button doesn't get instantiated
                    }
                })
                .setNegativeButton(R.string.btn_cancel, null)
                .create();
    }

    @Override
    public void onStart() {
        super.onStart();
        AlertDialog d = (AlertDialog)getDialog();
        if (d != null) {
            Button positiveButton = d.getButton(Dialog.BUTTON_POSITIVE);
            positiveButton.setOnClickListener(v -> {
                // save the data
            });
        }
    }

    private void updateVisibility(AutomaticBackupType tmpAutomaticBackupType) {
        if (tmpAutomaticBackupType != null) {
            UiHelper.setVisibility(timePart, !tmpAutomaticBackupType.equals(AutomaticBackupType.disabled), true);
        }
    }
}

Solution

  • Just found a solution (I do not like it, but it works).

    I am posting it here in case someone else will need it.

    It seems that TimePicker has the Positive/Natural/Negative buttons with the same id as the AlertDialog. When I set them using the Dialog builder, it actually set the one in the TimePicker.

    In general, the solution is to add a view to the Dialog without the TimePicker view and add it only after the buttons are set.

    The code above changed in two location: First in onCreateDialog method in the time settings block:

    {
        // time
        this.time.setIs24HourView(DateFormat.is24HourFormat(getActivity()));
        this.time.setCurrentHour(time.get(Calendar.HOUR_OF_DAY));
        this.time.setCurrentMinute(time.get(Calendar.MINUTE));
        timePart.removeView(this.time); // remove the TimePicker view from the layout
    }
    

    Second in the onStart method

    public void onStart() {
        super.onStart();
        AlertDialog d = (AlertDialog)getDialog();
        if (d != null) {
            Button positiveButton = d.getButton(Dialog.BUTTON_POSITIVE);
            timePart.addView(time); // Now we can add the view
            positiveButton.setOnClickListener(v -> {
                // save the data
            });
        }
    }