From 4664fe177bc1f498a405ed0ca220748dc6913a85 Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Thu, 9 Apr 2026 02:48:55 -0600 Subject: [PATCH] fixed spinner infinite loop in appointments --- .../AppointmentDetailFragment.java | 2 +- .../petstoremobile/utils/SpinnerUtils.java | 55 ++++++++++++++++--- .../example/petstoremobile/utils/UIUtils.java | 31 ++++++++--- 3 files changed, 69 insertions(+), 19 deletions(-) diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AppointmentDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AppointmentDetailFragment.java index d43304c0..1d280270 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AppointmentDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AppointmentDetailFragment.java @@ -176,7 +176,7 @@ public class AppointmentDetailFragment extends Fragment { */ private void applyViewState(AppointmentDetailViewModel.ViewState state) { isUpdatingUI = true; - + // Mode specific UI binding.tvApptMode.setText(state.isEditing ? "Edit Appointment" : "Add Appointment"); binding.tvAppointmentId.setText(DateTimeUtils.formatId(appointmentViewModel.getAppointmentId())); diff --git a/android/app/src/main/java/com/example/petstoremobile/utils/SpinnerUtils.java b/android/app/src/main/java/com/example/petstoremobile/utils/SpinnerUtils.java index 9c5385c7..de1e6304 100644 --- a/android/app/src/main/java/com/example/petstoremobile/utils/SpinnerUtils.java +++ b/android/app/src/main/java/com/example/petstoremobile/utils/SpinnerUtils.java @@ -10,6 +10,7 @@ import com.example.petstoremobile.adapters.BlackTextArrayAdapter; import com.example.petstoremobile.adapters.WhiteTextArrayAdapter; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.function.Consumer; @@ -51,6 +52,12 @@ public class SpinnerUtils { names.add(nameExtractor.apply(item)); } + // Only update adapter if contents changed to remove infinite loop when spinner is opened + if (isAdapterDataSame(spinner, names)) { + setSelectedId(spinner, data, defaultText, preselectedId, idExtractor); + return; + } + ArrayAdapter adapter; if (useWhiteText) { adapter = new WhiteTextArrayAdapter<>(context, android.R.layout.simple_spinner_item, names); @@ -61,26 +68,41 @@ public class SpinnerUtils { adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(adapter); + setSelectedId(spinner, data, defaultText, preselectedId, idExtractor); + } + + private static void setSelectedId(Spinner spinner, List data, String defaultText, Long preselectedId, Function idExtractor) { if (preselectedId != null && preselectedId != -1) { int offset = (defaultText != null) ? 1 : 0; for (int i = 0; i < data.size(); i++) { Long currentId = idExtractor.apply(data.get(i)); if (Objects.equals(currentId, preselectedId)) { - spinner.setSelection(i + offset); + if (spinner.getSelectedItemPosition() != i + offset) { + spinner.setSelection(i + offset); + } break; } } } } + /** + * Checks if the adapter data is the same as the new data. + */ + private static boolean isAdapterDataSame(Spinner spinner, List newNames) { + if (spinner.getAdapter() == null) return false; + if (spinner.getAdapter().getCount() != newNames.size()) return false; + for (int i = 0; i < newNames.size(); i++) { + if (!Objects.equals(spinner.getAdapter().getItem(i), newNames.get(i))) return false; + } + return true; + } + /** * Sets up a simple string spinner for filtering with a callback. */ public static void setupStringFilterSpinner(Context context, Spinner spinner, String[] items, Runnable onSelectionChanged) { - WhiteTextArrayAdapter adapter = new WhiteTextArrayAdapter<>(context, - android.R.layout.simple_spinner_item, items); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spinner.setAdapter(adapter); + updateStringSpinnerIfChanged(context, spinner, items, true); setupFilterSpinner(spinner, onSelectionChanged); } @@ -111,7 +133,7 @@ public class SpinnerUtils { if (value == null || spinner.getAdapter() == null) return; ArrayAdapter adapter = (ArrayAdapter) spinner.getAdapter(); int pos = adapter.getPosition(value); - if (pos >= 0) { + if (pos >= 0 && spinner.getSelectedItemPosition() != pos) { spinner.setSelection(pos); } } @@ -123,7 +145,9 @@ public class SpinnerUtils { if (spinner == null || array == null || value == null) return; for (int i = 0; i < array.length; i++) { if (Objects.equals(array[i], value)) { - spinner.setSelection(i); + if (spinner.getSelectedItemPosition() != i) { + spinner.setSelection(i); + } return; } } @@ -133,8 +157,21 @@ public class SpinnerUtils { * Configures a simple string array spinner. */ public static void setupStringSpinner(Context context, Spinner spinner, String[] items) { - BlackTextArrayAdapter adapter = new BlackTextArrayAdapter<>(context, - android.R.layout.simple_spinner_item, items); + updateStringSpinnerIfChanged(context, spinner, items, false); + } + + /** + * Updates a string spinner only if the items have changed. + */ + public static void updateStringSpinnerIfChanged(Context context, Spinner spinner, String[] items, boolean useWhiteText) { + if (isAdapterDataSame(spinner, Arrays.asList(items))) return; + + ArrayAdapter adapter; + if (useWhiteText) { + adapter = new WhiteTextArrayAdapter<>(context, android.R.layout.simple_spinner_item, items); + } else { + adapter = new BlackTextArrayAdapter<>(context, android.R.layout.simple_spinner_item, items); + } adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(adapter); } diff --git a/android/app/src/main/java/com/example/petstoremobile/utils/UIUtils.java b/android/app/src/main/java/com/example/petstoremobile/utils/UIUtils.java index 8a70621b..09b98cb6 100644 --- a/android/app/src/main/java/com/example/petstoremobile/utils/UIUtils.java +++ b/android/app/src/main/java/com/example/petstoremobile/utils/UIUtils.java @@ -73,36 +73,49 @@ public class UIUtils { } /** - * Sets the enabled state and alpha for multiple views. + * Sets the enabled state and alpha for multiple views, only if changed. */ public static void setViewsEnabled(boolean enabled, View... views) { for (View v : views) { if (v != null) { - v.setEnabled(enabled); - v.setAlpha(enabled ? 1.0f : 0.5f); + if (v.isEnabled() != enabled) { + v.setEnabled(enabled); + } + float targetAlpha = enabled ? 1.0f : 0.5f; + if (Math.abs(v.getAlpha() - targetAlpha) > 0.01f) { + v.setAlpha(targetAlpha); + } } } } /** - * Sets enabled state for a field and updates alpha for both the field and its label. + * Sets enabled state for a field and updates alpha for both the field and its label, only if changed. */ public static void setFieldEnabled(boolean enabled, View field, View label) { if (field != null) { - field.setEnabled(enabled); - field.setAlpha(enabled ? 1.0f : 0.5f); + if (field.isEnabled() != enabled) { + field.setEnabled(enabled); + } + float targetAlpha = enabled ? 1.0f : 0.5f; + if (Math.abs(field.getAlpha() - targetAlpha) > 0.01f) { + field.setAlpha(targetAlpha); + } } if (label != null) { - label.setAlpha(enabled ? 1.0f : 0.5f); + float targetAlpha = enabled ? 1.0f : 0.5f; + if (Math.abs(label.getAlpha() - targetAlpha) > 0.01f) { + label.setAlpha(targetAlpha); + } } } /** - * Sets the alpha for multiple views. + * Sets the alpha for multiple views, only if changed. */ public static void setViewsAlpha(float alpha, View... views) { for (View v : views) { - if (v != null) { + if (v != null && Math.abs(v.getAlpha() - alpha) > 0.01f) { v.setAlpha(alpha); } }