diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AdoptionDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AdoptionDetailFragment.java index c14ae13a..0455f457 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AdoptionDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AdoptionDetailFragment.java @@ -113,6 +113,7 @@ public class AdoptionDetailFragment extends Fragment { binding.spinnerAdoptionCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (isUpdatingUI) return; viewModel.onCustomerSelected(position); } @Override @@ -122,6 +123,7 @@ public class AdoptionDetailFragment extends Fragment { binding.spinnerAdoptionStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (isUpdatingUI) return; viewModel.onStoreSelected(position); } @Override 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 e08f7fdc..3e0e20af 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 @@ -89,6 +89,7 @@ public class AppointmentDetailFragment extends Fragment { binding.spinnerCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (isUpdatingUI) return; viewModel.onCustomerSelected(position); } @Override @@ -98,6 +99,7 @@ public class AppointmentDetailFragment extends Fragment { binding.spinnerStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (isUpdatingUI) return; viewModel.onStoreSelected(position); } @Override diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionDetailViewModel.java index aa4a78cb..06b96595 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionDetailViewModel.java @@ -13,6 +13,8 @@ import com.example.petstoremobile.repositories.StoreRepository; import com.example.petstoremobile.utils.DateTimeUtils; import com.example.petstoremobile.utils.Resource; +import androidx.lifecycle.Observer; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -71,18 +73,17 @@ public class AdoptionDetailViewModel extends ViewModel { state.saveButtonText = isEditing ? "Save" : "Add"; state.isAdoptionIdVisible = isEditing; state.isDeleteVisible = isEditing; - state.isFeeEnabled = false; // fee is always read-only + state.isFeeEnabled = false; if (!isEditing) { state.isCustomerEnabled = true; state.isStoreEnabled = true; - state.isPetEnabled = false; // until customer selected - state.isEmployeeEnabled = false; // until store selected + state.isPetEnabled = false; + state.isEmployeeEnabled = false; state.isDateEnabled = true; state.isStatusEnabled = true; state.availableStatuses = new String[]{"Pending"}; state.selectedStatus = "Pending"; } else { - // edit: date-based logic applied after load state.isCustomerEnabled = false; state.isStoreEnabled = false; state.isPetEnabled = false; @@ -95,12 +96,12 @@ public class AdoptionDetailViewModel extends ViewModel { public void loadInitialFormData(boolean isEditing) { // Pets are loaded dynamically based on store selection; no pre-load needed. - customerRepository.getCustomerDropdowns().observeForever(r -> { + observeOnce(customerRepository.getCustomerDropdowns(), r -> { if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) { customerList.setValue(r.data); } }); - storeRepository.getStoreDropdowns().observeForever(r -> { + observeOnce(storeRepository.getStoreDropdowns(), r -> { if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) { storeList.setValue(r.data); } @@ -155,7 +156,7 @@ public class AdoptionDetailViewModel extends ViewModel { } private void loadAvailablePetsByStore(Long storeId) { - petRepository.getAvailablePetsByStore(storeId).observeForever(r -> { + observeOnce(petRepository.getAvailablePetsByStore(storeId), r -> { if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) { List dropdowns = new ArrayList<>(); for (com.example.petstoremobile.dtos.PetDTO pet : r.data.getContent()) { @@ -167,7 +168,7 @@ public class AdoptionDetailViewModel extends ViewModel { } private void loadPetPrice(Long petId) { - petRepository.getPetById(petId).observeForever(r -> { + observeOnce(petRepository.getPetById(petId), r -> { if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) { com.example.petstoremobile.dtos.PetDTO pet = r.data; // In edit mode, add the pet to the list so the spinner can display its name @@ -185,7 +186,7 @@ public class AdoptionDetailViewModel extends ViewModel { } private void loadEmployeesForStore(Long storeId) { - storeRepository.getStoreEmployees(storeId).observeForever(r -> { + observeOnce(storeRepository.getStoreEmployees(storeId), r -> { if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) { employeeList.setValue(r.data); } @@ -217,6 +218,10 @@ public class AdoptionDetailViewModel extends ViewModel { s.isEmployeeEnabled = true; s.isDateEnabled = true; s.isStatusEnabled = true; + } else { + // Date cleared: disable everything except the date field so user can pick again + setAllEditableFieldsEnabled(s, false); + s.isDateEnabled = true; } }); } @@ -240,7 +245,7 @@ public class AdoptionDetailViewModel extends ViewModel { public LiveData> loadAdoption() { MutableLiveData> result = new MutableLiveData<>(); - adoptionRepository.getAdoptionById(adoptionId).observeForever(resource -> { + observeOnce(adoptionRepository.getAdoptionById(adoptionId), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { AdoptionDTO a = resource.data; String formattedStatus = DateTimeUtils.formatStatusFromBackend( @@ -321,6 +326,21 @@ public class AdoptionDetailViewModel extends ViewModel { void run(T target); } + /** + * Observes a LiveData once, removing the observer after the first response. + * */ + private void observeOnce(LiveData> liveData, Observer> handler) { + liveData.observeForever(new Observer>() { + @Override + public void onChanged(Resource resource) { + if (resource == null || resource.status != Resource.Status.LOADING) { + liveData.removeObserver(this); + } + handler.onChanged(resource); + } + }); + } + public static class ViewState { public boolean isEditing = false; public boolean isAdoptionIdVisible = false; diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionListViewModel.java index 7c0b72b8..d008e5a4 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionListViewModel.java @@ -11,6 +11,8 @@ import com.example.petstoremobile.repositories.AdoptionRepository; import com.example.petstoremobile.repositories.StoreRepository; import com.example.petstoremobile.utils.Resource; +import androidx.lifecycle.Observer; + import java.util.ArrayList; import java.util.List; @@ -53,7 +55,7 @@ public class AdoptionListViewModel extends ViewModel { if ("All Statuses".equals(status)) status = null; isLoading.setValue(true); - adoptionRepository.getAllAdoptions(currentPage, PAGE_SIZE, query, status, storeId, date, employeeId).observeForever(resource -> { + observeOnce(adoptionRepository.getAllAdoptions(currentPage, PAGE_SIZE, query, status, storeId, date, employeeId), resource -> { if (resource != null) { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { List currentList = reset ? new ArrayList<>() : new ArrayList<>(adoptions.getValue()); @@ -70,13 +72,25 @@ public class AdoptionListViewModel extends ViewModel { } public void loadStores() { - storeRepository.getAllStores(0, 100).observeForever(resource -> { + observeOnce(storeRepository.getAllStores(0, 100), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { stores.setValue(resource.data.getContent()); } }); } + private void observeOnce(LiveData> liveData, Observer> handler) { + liveData.observeForever(new Observer>() { + @Override + public void onChanged(Resource resource) { + if (resource == null || resource.status != Resource.Status.LOADING) { + liveData.removeObserver(this); + } + handler.onChanged(resource); + } + }); + } + public LiveData> bulkDeleteAdoptions(List ids) { return adoptionRepository.bulkDeleteAdoptions(new BulkDeleteRequest(ids)); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AnalyticsViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AnalyticsViewModel.java index 8d43d995..3e5082ec 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AnalyticsViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AnalyticsViewModel.java @@ -8,6 +8,8 @@ import com.example.petstoremobile.dtos.SaleDTO; import com.example.petstoremobile.repositories.SaleRepository; import com.example.petstoremobile.utils.Resource; +import androidx.lifecycle.Observer; + import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; @@ -49,7 +51,7 @@ public class AnalyticsViewModel extends ViewModel { public void loadAnalytics() { isLoading.setValue(true); errorMessage.setValue(null); - saleRepository.getAllSales(0, 2000, null, null, null, null, "saleDate,desc").observeForever(resource -> { + observeOnce(saleRepository.getAllSales(0, 2000, null, null, null, null, "saleDate,desc"), resource -> { if (resource != null) { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { cachedSales = resource.data.getContent(); @@ -247,6 +249,18 @@ public class AnalyticsViewModel extends ViewModel { return "Daily Revenue (" + s + " – " + e + ")"; } + private void observeOnce(LiveData> liveData, Observer> handler) { + liveData.observeForever(new Observer>() { + @Override + public void onChanged(Resource resource) { + if (resource == null || resource.status != Resource.Status.LOADING) { + liveData.removeObserver(this); + } + handler.onChanged(resource); + } + }); + } + public static class FilterState { public String startDate = ""; public String endDate = ""; diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentDetailViewModel.java index fae681d9..c70c1895 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentDetailViewModel.java @@ -15,6 +15,8 @@ import com.example.petstoremobile.repositories.StoreRepository; import com.example.petstoremobile.utils.DateTimeUtils; import com.example.petstoremobile.utils.Resource; +import androidx.lifecycle.Observer; + import java.util.ArrayList; import java.util.List; @@ -71,13 +73,13 @@ public class AppointmentDetailViewModel extends ViewModel { * Loads initial dropdown data for customers, stores, and services. */ public void loadInitialFormData() { - customerRepository.getCustomerDropdowns().observeForever(r -> { + observeOnce(customerRepository.getCustomerDropdowns(), r -> { if (r != null && r.status == Resource.Status.SUCCESS) customers.setValue(r.data); }); - storeRepository.getStoreDropdowns().observeForever(r -> { + observeOnce(storeRepository.getStoreDropdowns(), r -> { if (r != null && r.status == Resource.Status.SUCCESS) stores.setValue(r.data); }); - serviceRepository.getAllServices(0, 200, null, "serviceName").observeForever(r -> { + observeOnce(serviceRepository.getAllServices(0, 200, null, "serviceName"), r -> { if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) services.setValue(r.data.getContent()); }); } @@ -206,7 +208,7 @@ public class AppointmentDetailViewModel extends ViewModel { * Loads the list of pets for a specific customer. */ private void loadPetsForCustomer(Long customerId) { - petRepository.getCustomerPets(customerId).observeForever(r -> { + observeOnce(petRepository.getCustomerPets(customerId), r -> { if (r != null && r.status == Resource.Status.SUCCESS) customerPets.setValue(r.data); }); } @@ -215,7 +217,7 @@ public class AppointmentDetailViewModel extends ViewModel { * Loads the list of employees for a specific store. */ private void loadEmployeesForStore(Long storeId) { - storeRepository.getStoreEmployees(storeId).observeForever(r -> { + observeOnce(storeRepository.getStoreEmployees(storeId), r -> { if (r != null && r.status == Resource.Status.SUCCESS) storeEmployees.setValue(r.data); }); } @@ -227,7 +229,7 @@ public class AppointmentDetailViewModel extends ViewModel { */ public LiveData> loadAppointment() { MutableLiveData> result = new MutableLiveData<>(); - repository.getAppointmentById(appointmentId).observeForever(resource -> { + observeOnce(repository.getAppointmentById(appointmentId), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { AppointmentDTO a = resource.data; isOriginallyCancel = "CANCELLED".equalsIgnoreCase(a.getAppointmentStatus()); @@ -385,6 +387,19 @@ public class AppointmentDetailViewModel extends ViewModel { void run(T t); } + /** Observes a LiveData once, removing the observer after the first non-loading response. */ + private void observeOnce(LiveData> liveData, Observer> handler) { + liveData.observeForever(new Observer>() { + @Override + public void onChanged(Resource resource) { + if (resource == null || resource.status != Resource.Status.LOADING) { + liveData.removeObserver(this); + } + handler.onChanged(resource); + } + }); + } + /** * A Class to show the states of Appointment Detail Fragment. */ diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentListViewModel.java index 8bdaf699..31020d12 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentListViewModel.java @@ -11,6 +11,8 @@ import com.example.petstoremobile.repositories.AppointmentRepository; import com.example.petstoremobile.repositories.StoreRepository; import com.example.petstoremobile.utils.Resource; +import androidx.lifecycle.Observer; + import java.util.ArrayList; import java.util.List; @@ -39,7 +41,7 @@ public class AppointmentListViewModel extends ViewModel { public void loadAppointments(String query, String status, Long storeId, String date, Long employeeId) { isLoading.setValue(true); - appointmentRepository.getAllAppointments(0, 500, query, status, storeId, date, employeeId).observeForever(resource -> { + observeOnce(appointmentRepository.getAllAppointments(0, 500, query, status, storeId, date, employeeId), resource -> { if (resource != null) { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { appointments.setValue(resource.data.getContent()); @@ -52,13 +54,25 @@ public class AppointmentListViewModel extends ViewModel { } public void loadStores() { - storeRepository.getAllStores(0, 100).observeForever(resource -> { + observeOnce(storeRepository.getAllStores(0, 100), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { stores.setValue(resource.data.getContent()); } }); } + private void observeOnce(LiveData> liveData, Observer> handler) { + liveData.observeForever(new Observer>() { + @Override + public void onChanged(Resource resource) { + if (resource == null || resource.status != Resource.Status.LOADING) { + liveData.removeObserver(this); + } + handler.onChanged(resource); + } + }); + } + public LiveData> bulkDeleteAppointments(List ids) { return appointmentRepository.bulkDeleteAppointments(new BulkDeleteRequest(ids)); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/CouponListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/CouponListViewModel.java index ad27ae6c..e1ea12f8 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/CouponListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/CouponListViewModel.java @@ -9,6 +9,8 @@ import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.repositories.CouponRepository; import com.example.petstoremobile.utils.Resource; +import androidx.lifecycle.Observer; + import java.util.ArrayList; import java.util.List; @@ -32,7 +34,7 @@ public class CouponListViewModel extends ViewModel { public void loadCoupons(int page, int size, Boolean active, String discountType, String sort) { isLoading.setValue(true); - repository.getAllCoupons(page, size, active, discountType, sort).observeForever(resource -> { + observeOnce(repository.getAllCoupons(page, size, active, discountType, sort), resource -> { if (resource != null) { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { coupons.setValue(resource.data.getContent()); @@ -44,6 +46,18 @@ public class CouponListViewModel extends ViewModel { }); } + private void observeOnce(LiveData> liveData, Observer> handler) { + liveData.observeForever(new Observer>() { + @Override + public void onChanged(Resource resource) { + if (resource == null || resource.status != Resource.Status.LOADING) { + liveData.removeObserver(this); + } + handler.onChanged(resource); + } + }); + } + public LiveData> bulkDeleteCoupons(List ids) { return repository.bulkDeleteCoupons(ids); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/CustomerListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/CustomerListViewModel.java index 241fc811..1d7c18ba 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/CustomerListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/CustomerListViewModel.java @@ -8,6 +8,8 @@ import com.example.petstoremobile.dtos.CustomerDTO; import com.example.petstoremobile.repositories.CustomerRepository; import com.example.petstoremobile.utils.Resource; +import androidx.lifecycle.Observer; + import java.util.ArrayList; import java.util.List; @@ -36,7 +38,7 @@ public class CustomerListViewModel extends ViewModel { public void loadCustomers() { isLoading.setValue(true); - repository.getAllCustomers(0, 200).observeForever(resource -> { + observeOnce(repository.getAllCustomers(0, 200), resource -> { if (resource != null) { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { customers.setValue(resource.data.getContent()); @@ -49,6 +51,18 @@ public class CustomerListViewModel extends ViewModel { }); } + private void observeOnce(LiveData> liveData, Observer> handler) { + liveData.observeForever(new Observer>() { + @Override + public void onChanged(Resource resource) { + if (resource == null || resource.status != Resource.Status.LOADING) { + liveData.removeObserver(this); + } + handler.onChanged(resource); + } + }); + } + public void filter(String query, String status) { this.lastQuery = query; this.lastStatus = status; diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryListViewModel.java index c91f8337..66387983 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryListViewModel.java @@ -11,6 +11,8 @@ import com.example.petstoremobile.repositories.InventoryRepository; import com.example.petstoremobile.repositories.StoreRepository; import com.example.petstoremobile.utils.Resource; +import androidx.lifecycle.Observer; + import java.util.ArrayList; import java.util.List; @@ -51,7 +53,7 @@ public class InventoryListViewModel extends ViewModel { } isLoading.setValue(true); - inventoryRepository.getAllInventory(query, storeId, currentPage, PAGE_SIZE, "product.prodName").observeForever(resource -> { + observeOnce(inventoryRepository.getAllInventory(query, storeId, currentPage, PAGE_SIZE, "product.prodName"), resource -> { if (resource != null) { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { List currentList = reset ? new ArrayList<>() : new ArrayList<>(inventory.getValue()); @@ -68,13 +70,25 @@ public class InventoryListViewModel extends ViewModel { } public void loadStores() { - storeRepository.getAllStores(0, 100).observeForever(resource -> { + observeOnce(storeRepository.getAllStores(0, 100), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { stores.setValue(resource.data.getContent()); } }); } + private void observeOnce(LiveData> liveData, Observer> handler) { + liveData.observeForever(new Observer>() { + @Override + public void onChanged(Resource resource) { + if (resource == null || resource.status != Resource.Status.LOADING) { + liveData.removeObserver(this); + } + handler.onChanged(resource); + } + }); + } + public LiveData> bulkDeleteInventory(List ids) { return inventoryRepository.bulkDeleteInventory(new BulkDeleteRequest(ids)); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetDetailViewModel.java index 44f2680a..3a0f4482 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetDetailViewModel.java @@ -11,6 +11,8 @@ import com.example.petstoremobile.repositories.PetRepository; import com.example.petstoremobile.repositories.StoreRepository; import com.example.petstoremobile.utils.Resource; +import androidx.lifecycle.Observer; + import java.util.ArrayList; import java.util.List; @@ -45,13 +47,13 @@ public class PetDetailViewModel extends ViewModel { } public void loadInitialFormData() { - customerRepository.getCustomerDropdowns().observeForever(resource -> { + observeOnce(customerRepository.getCustomerDropdowns(), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { customerList.setValue(resource.data); } }); - storeRepository.getStoreDropdowns().observeForever(resource -> { + observeOnce(storeRepository.getStoreDropdowns(), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { storeList.setValue(resource.data); } @@ -134,7 +136,7 @@ public class PetDetailViewModel extends ViewModel { public LiveData> loadPet() { MutableLiveData> result = new MutableLiveData<>(); - petRepository.getPetById(petId).observeForever(resource -> { + observeOnce(petRepository.getPetById(petId), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { PetDTO pet = resource.data; selectedCustomerId = pet.getCustomerId(); @@ -227,6 +229,17 @@ public class PetDetailViewModel extends ViewModel { void run(T target); } + private void observeOnce(LiveData> liveData, Observer> handler) { + liveData.observeForever(new Observer>() { + @Override + public void onChanged(Resource resource) { + if (resource == null || resource.status != Resource.Status.LOADING) { + liveData.removeObserver(this); + } + handler.onChanged(resource); + } + }); + } public static class ViewState { public boolean isEditing = false; diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetListViewModel.java index 8a567450..2c1132ae 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetListViewModel.java @@ -12,6 +12,8 @@ import com.example.petstoremobile.repositories.PetRepository; import com.example.petstoremobile.repositories.StoreRepository; import com.example.petstoremobile.utils.Resource; +import androidx.lifecycle.Observer; + import java.util.ArrayList; import java.util.List; @@ -43,7 +45,7 @@ public class PetListViewModel extends ViewModel { if ("All Species".equals(species)) species = null; isLoading.setValue(true); - petRepository.getAllPets(0, 100, query, status, species, storeId, null, "petName").observeForever(resource -> { + observeOnce(petRepository.getAllPets(0, 100, query, status, species, storeId, null, "petName"), resource -> { if (resource != null) { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { pets.setValue(resource.data.getContent()); @@ -56,13 +58,25 @@ public class PetListViewModel extends ViewModel { } public void loadStores() { - storeRepository.getAllStores(0, 100).observeForever(resource -> { + observeOnce(storeRepository.getAllStores(0, 100), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { stores.setValue(resource.data.getContent()); } }); } + private void observeOnce(LiveData> liveData, Observer> handler) { + liveData.observeForever(new Observer>() { + @Override + public void onChanged(Resource resource) { + if (resource == null || resource.status != Resource.Status.LOADING) { + liveData.removeObserver(this); + } + handler.onChanged(resource); + } + }); + } + public LiveData> bulkDeletePets(List ids) { return petRepository.bulkDeletePets(new BulkDeleteRequest(ids)); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductListViewModel.java index 6d89ab6f..503bf8b6 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductListViewModel.java @@ -10,6 +10,8 @@ import com.example.petstoremobile.repositories.CategoryRepository; import com.example.petstoremobile.repositories.ProductRepository; import com.example.petstoremobile.utils.Resource; +import androidx.lifecycle.Observer; + import java.util.ArrayList; import java.util.List; @@ -38,7 +40,7 @@ public class ProductListViewModel extends ViewModel { public void loadProducts(String query, Long categoryId) { isLoading.setValue(true); - productRepository.getAllProducts(query, categoryId, 0, 100, "prodName").observeForever(resource -> { + observeOnce(productRepository.getAllProducts(query, categoryId, 0, 100, "prodName"), resource -> { if (resource != null) { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { products.setValue(resource.data.getContent()); @@ -51,10 +53,22 @@ public class ProductListViewModel extends ViewModel { } public void loadCategories() { - productRepository.getCategoryDropdowns().observeForever(resource -> { + observeOnce(productRepository.getCategoryDropdowns(), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { categories.setValue(resource.data); } }); } + + private void observeOnce(LiveData> liveData, Observer> handler) { + liveData.observeForever(new Observer>() { + @Override + public void onChanged(Resource resource) { + if (resource == null || resource.status != Resource.Status.LOADING) { + liveData.removeObserver(this); + } + handler.onChanged(resource); + } + }); + } } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierListViewModel.java index cad846c4..25f7b8a1 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierListViewModel.java @@ -13,6 +13,8 @@ import com.example.petstoremobile.repositories.ProductSupplierRepository; import com.example.petstoremobile.repositories.SupplierRepository; import com.example.petstoremobile.utils.Resource; +import androidx.lifecycle.Observer; + import java.util.ArrayList; import java.util.List; @@ -45,7 +47,7 @@ public class ProductSupplierListViewModel extends ViewModel { public void loadProductSuppliers(String query, Long productId, Long supplierId) { isLoading.setValue(true); - psRepository.getAllProductSuppliers(0, 100, query, productId, supplierId, "productName").observeForever(resource -> { + observeOnce(psRepository.getAllProductSuppliers(0, 100, query, productId, supplierId, "productName"), resource -> { if (resource != null) { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { productSuppliers.setValue(resource.data.getContent()); @@ -58,19 +60,31 @@ public class ProductSupplierListViewModel extends ViewModel { } public void loadFilterData() { - productRepository.getAllProducts(null, null, 0, 100, "prodName").observeForever(resource -> { + observeOnce(productRepository.getAllProducts(null, null, 0, 100, "prodName"), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { products.setValue(resource.data.getContent()); } }); - supplierRepository.getAllSuppliers(0, 100, null, "supCompany").observeForever(resource -> { + observeOnce(supplierRepository.getAllSuppliers(0, 100, null, "supCompany"), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { suppliers.setValue(resource.data.getContent()); } }); } + private void observeOnce(LiveData> liveData, Observer> handler) { + liveData.observeForever(new Observer>() { + @Override + public void onChanged(Resource resource) { + if (resource == null || resource.status != Resource.Status.LOADING) { + liveData.removeObserver(this); + } + handler.onChanged(resource); + } + }); + } + public LiveData> bulkDeleteProductSuppliers(List ids) { return psRepository.bulkDeleteProductSuppliers(new BulkDeleteRequest(ids)); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderListViewModel.java index 438f4198..296e4792 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderListViewModel.java @@ -10,6 +10,8 @@ import com.example.petstoremobile.repositories.PurchaseOrderRepository; import com.example.petstoremobile.repositories.StoreRepository; import com.example.petstoremobile.utils.Resource; +import androidx.lifecycle.Observer; + import java.util.ArrayList; import java.util.List; @@ -38,7 +40,7 @@ public class PurchaseOrderListViewModel extends ViewModel { public void loadPurchaseOrders(String query, Long storeId) { isLoading.setValue(true); - purchaseOrderRepository.getAllPurchaseOrders(0, 100, query, storeId, "purchaseOrderId,desc").observeForever(resource -> { + observeOnce(purchaseOrderRepository.getAllPurchaseOrders(0, 100, query, storeId, "purchaseOrderId,desc"), resource -> { if (resource != null) { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { purchaseOrders.setValue(resource.data.getContent()); @@ -51,10 +53,22 @@ public class PurchaseOrderListViewModel extends ViewModel { } public void loadStores() { - storeRepository.getAllStores(0, 100).observeForever(resource -> { + observeOnce(storeRepository.getAllStores(0, 100), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { stores.setValue(resource.data.getContent()); } }); } + + private void observeOnce(LiveData> liveData, Observer> handler) { + liveData.observeForever(new Observer>() { + @Override + public void onChanged(Resource resource) { + if (resource == null || resource.status != Resource.Status.LOADING) { + liveData.removeObserver(this); + } + handler.onChanged(resource); + } + }); + } } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/SaleListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SaleListViewModel.java index 297d987f..7df7269a 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/SaleListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SaleListViewModel.java @@ -11,6 +11,8 @@ import com.example.petstoremobile.repositories.SaleRepository; import com.example.petstoremobile.repositories.StoreRepository; import com.example.petstoremobile.utils.Resource; +import androidx.lifecycle.Observer; + import java.util.ArrayList; import java.util.List; @@ -51,7 +53,7 @@ public class SaleListViewModel extends ViewModel { } isLoading.setValue(true); - saleRepository.getAllSales(currentPage, PAGE_SIZE, query, paymentMethod, storeId, isRefund, "saleDate,desc").observeForever(resource -> { + observeOnce(saleRepository.getAllSales(currentPage, PAGE_SIZE, query, paymentMethod, storeId, isRefund, "saleDate,desc"), resource -> { if (resource != null) { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { List currentList = reset ? new ArrayList<>() : new ArrayList<>(sales.getValue()); @@ -68,10 +70,22 @@ public class SaleListViewModel extends ViewModel { } public void loadStores() { - storeRepository.getAllStores(0, 100).observeForever(resource -> { + observeOnce(storeRepository.getAllStores(0, 100), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { stores.setValue(resource.data.getContent()); } }); } + + private void observeOnce(LiveData> liveData, Observer> handler) { + liveData.observeForever(new Observer>() { + @Override + public void onChanged(Resource resource) { + if (resource == null || resource.status != Resource.Status.LOADING) { + liveData.removeObserver(this); + } + handler.onChanged(resource); + } + }); + } } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceDetailViewModel.java index aa465a08..625abb92 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceDetailViewModel.java @@ -8,6 +8,8 @@ import com.example.petstoremobile.dtos.ServiceDTO; import com.example.petstoremobile.repositories.ServiceRepository; import com.example.petstoremobile.utils.Resource; +import androidx.lifecycle.Observer; + import javax.inject.Inject; import dagger.hilt.android.lifecycle.HiltViewModel; @@ -55,7 +57,7 @@ public class ServiceDetailViewModel extends ViewModel { public LiveData> loadService() { MutableLiveData> result = new MutableLiveData<>(); - repository.getServiceById(serviceId).observeForever(resource -> { + observeOnce(repository.getServiceById(serviceId), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { ServiceDTO service = resource.data; updateViewState(state -> { @@ -106,6 +108,18 @@ public class ServiceDetailViewModel extends ViewModel { void run(T target); } + private void observeOnce(LiveData> liveData, Observer> handler) { + liveData.observeForever(new Observer>() { + @Override + public void onChanged(Resource resource) { + if (resource == null || resource.status != Resource.Status.LOADING) { + liveData.removeObserver(this); + } + handler.onChanged(resource); + } + }); + } + public static class ViewState { public boolean isEditing = false; public boolean isDeleteVisible = false; diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceListViewModel.java index d0fa121b..bbdbe270 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceListViewModel.java @@ -10,6 +10,8 @@ import com.example.petstoremobile.dtos.ServiceDTO; import com.example.petstoremobile.repositories.ServiceRepository; import com.example.petstoremobile.utils.Resource; +import androidx.lifecycle.Observer; + import java.util.ArrayList; import java.util.List; @@ -46,7 +48,7 @@ public class ServiceListViewModel extends ViewModel { } isLoading.setValue(true); - repository.getAllServices(currentPage, PAGE_SIZE, query, "serviceName").observeForever(resource -> { + observeOnce(repository.getAllServices(currentPage, PAGE_SIZE, query, "serviceName"), resource -> { if (resource != null) { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { List currentList = reset ? new ArrayList<>() : new ArrayList<>(services.getValue()); @@ -62,6 +64,18 @@ public class ServiceListViewModel extends ViewModel { }); } + private void observeOnce(LiveData> liveData, Observer> handler) { + liveData.observeForever(new Observer>() { + @Override + public void onChanged(Resource resource) { + if (resource == null || resource.status != Resource.Status.LOADING) { + liveData.removeObserver(this); + } + handler.onChanged(resource); + } + }); + } + public LiveData> bulkDeleteServices(List ids) { return repository.bulkDeleteServices(new BulkDeleteRequest(ids)); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/StaffListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/StaffListViewModel.java index d448d853..023d1aaf 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/StaffListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/StaffListViewModel.java @@ -10,6 +10,8 @@ import com.example.petstoremobile.repositories.EmployeeRepository; import com.example.petstoremobile.repositories.StoreRepository; import com.example.petstoremobile.utils.Resource; +import androidx.lifecycle.Observer; + import java.util.ArrayList; import java.util.List; @@ -42,7 +44,7 @@ public class StaffListViewModel extends ViewModel { public void loadStaff() { isLoading.setValue(true); - repository.getAllEmployees(0, 100).observeForever(resource -> { + observeOnce(repository.getAllEmployees(0, 100), resource -> { if (resource != null) { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { employees.setValue(resource.data.getContent()); @@ -56,13 +58,25 @@ public class StaffListViewModel extends ViewModel { } public void loadStores() { - storeRepository.getAllStores(0, 100).observeForever(resource -> { + observeOnce(storeRepository.getAllStores(0, 100), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { stores.setValue(resource.data.getContent()); } }); } + private void observeOnce(LiveData> liveData, Observer> handler) { + liveData.observeForever(new Observer>() { + @Override + public void onChanged(Resource resource) { + if (resource == null || resource.status != Resource.Status.LOADING) { + liveData.removeObserver(this); + } + handler.onChanged(resource); + } + }); + } + public void filter(String query, Long storeId, String status) { this.lastQuery = query; this.lastStoreId = storeId; diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierDetailViewModel.java index 88078102..54f83ef9 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierDetailViewModel.java @@ -8,6 +8,8 @@ import com.example.petstoremobile.dtos.SupplierDTO; import com.example.petstoremobile.repositories.SupplierRepository; import com.example.petstoremobile.utils.Resource; +import androidx.lifecycle.Observer; + import javax.inject.Inject; import dagger.hilt.android.lifecycle.HiltViewModel; @@ -55,7 +57,7 @@ public class SupplierDetailViewModel extends ViewModel { public LiveData> loadSupplier() { MutableLiveData> result = new MutableLiveData<>(); - repository.getSupplierById(supId).observeForever(resource -> { + observeOnce(repository.getSupplierById(supId), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { SupplierDTO s = resource.data; updateViewState(state -> { @@ -99,6 +101,18 @@ public class SupplierDetailViewModel extends ViewModel { void run(T target); } + private void observeOnce(LiveData> liveData, Observer> handler) { + liveData.observeForever(new Observer>() { + @Override + public void onChanged(Resource resource) { + if (resource == null || resource.status != Resource.Status.LOADING) { + liveData.removeObserver(this); + } + handler.onChanged(resource); + } + }); + } + public static class ViewState { public boolean isEditing = false; public boolean isDeleteVisible = false; diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierListViewModel.java index 072ad3bd..462030ea 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierListViewModel.java @@ -9,6 +9,8 @@ import com.example.petstoremobile.dtos.SupplierDTO; import com.example.petstoremobile.repositories.SupplierRepository; import com.example.petstoremobile.utils.Resource; +import androidx.lifecycle.Observer; + import java.util.ArrayList; import java.util.List; @@ -33,7 +35,7 @@ public class SupplierListViewModel extends ViewModel { public void loadSuppliers(String query) { isLoading.setValue(true); - repository.getAllSuppliers(0, 100, query, "supCompany").observeForever(resource -> { + observeOnce(repository.getAllSuppliers(0, 100, query, "supCompany"), resource -> { if (resource != null) { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { suppliers.setValue(resource.data.getContent()); @@ -45,6 +47,18 @@ public class SupplierListViewModel extends ViewModel { }); } + private void observeOnce(LiveData> liveData, Observer> handler) { + liveData.observeForever(new Observer>() { + @Override + public void onChanged(Resource resource) { + if (resource == null || resource.status != Resource.Status.LOADING) { + liveData.removeObserver(this); + } + handler.onChanged(resource); + } + }); + } + public LiveData> bulkDeleteSuppliers(List ids) { return repository.bulkDeleteSuppliers(new BulkDeleteRequest(ids)); }