Skip to content

Commit 7f142f9

Browse files
committed
fix(feed): prioritize explicit savedFilter to fix selection race condition
Refactors the `_onHeadlinesFeedFiltersApplied` handler to prioritize the optional `event.savedFilter` when determining the `activeFilterId`. Previously, when saving and applying a new filter, a race condition occurred where the `HeadlinesFeedBloc` would perform its comparison logic before its state was updated with the new filter from `AppBloc`, causing it to incorrectly select the "Custom" chip. This change ensures that if `event.savedFilter` is provided, its ID is used directly, bypassing the comparison logic and guaranteeing the correct new filter is selected. The comparison logic is retained as a fallback for all other filter application scenarios.
1 parent 6e993e2 commit 7f142f9

File tree

1 file changed

+32
-21
lines changed

1 file changed

+32
-21
lines changed

lib/headlines-feed/bloc/headlines_feed_bloc.dart

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -266,28 +266,39 @@ class HeadlinesFeedBloc extends Bloc<HeadlinesFeedEvent, HeadlinesFeedState> {
266266
HeadlinesFeedFiltersApplied event,
267267
Emitter<HeadlinesFeedState> emit,
268268
) async {
269-
// Determine if the applied filter matches an existing saved filter.
270-
// This logic is crucial for correctly setting the active filter chip in
271-
// the UI. It compares the content of the applied filter with each saved
272-
// filter, ignoring the order of items within the lists.
273-
final matchingSavedFilter = state.savedFilters.firstWhereOrNull((
274-
savedFilter,
275-
) {
276-
// Use sets for order-agnostic comparison of filter contents.
277-
final appliedTopics = event.filter.topics?.toSet() ?? {};
278-
final savedTopics = savedFilter.topics.toSet();
279-
final appliedSources = event.filter.sources?.toSet() ?? {};
280-
final savedSources = savedFilter.sources.toSet();
281-
final appliedCountries = event.filter.eventCountries?.toSet() ?? {};
282-
final savedCountries = savedFilter.countries.toSet();
283-
284-
return const SetEquality<Topic>().equals(appliedTopics, savedTopics) &&
285-
const SetEquality<Source>().equals(appliedSources, savedSources) &&
286-
const SetEquality<Country>().equals(appliedCountries, savedCountries);
287-
});
269+
String newActiveFilterId;
270+
271+
// Prioritize the explicitly passed savedFilter to prevent race conditions.
272+
// This is crucial for the "save and apply" flow, where the AppBloc might
273+
// not have updated the savedFilters list in this bloc's state yet.
274+
if (event.savedFilter != null) {
275+
newActiveFilterId = event.savedFilter!.id;
276+
} else {
277+
// If no filter is explicitly passed, determine if the applied filter
278+
// matches an existing saved filter. This handles re-applying a saved
279+
// filter or applying a one-time "custom" filter.
280+
final matchingSavedFilter = state.savedFilters.firstWhereOrNull((
281+
savedFilter,
282+
) {
283+
// Use sets for order-agnostic comparison of filter contents.
284+
final appliedTopics = event.filter.topics?.toSet() ?? {};
285+
final savedTopics = savedFilter.topics.toSet();
286+
final appliedSources = event.filter.sources?.toSet() ?? {};
287+
final savedSources = savedFilter.sources.toSet();
288+
final appliedCountries = event.filter.eventCountries?.toSet() ?? {};
289+
final savedCountries = savedFilter.countries.toSet();
290+
291+
return const SetEquality<Topic>().equals(appliedTopics, savedTopics) &&
292+
const SetEquality<Source>().equals(appliedSources, savedSources) &&
293+
const SetEquality<Country>().equals(
294+
appliedCountries,
295+
savedCountries,
296+
);
297+
});
288298

289-
// If a match is found, use its ID. Otherwise, mark it as 'custom'.
290-
final newActiveFilterId = matchingSavedFilter?.id ?? 'custom';
299+
// If a match is found, use its ID. Otherwise, mark it as 'custom'.
300+
newActiveFilterId = matchingSavedFilter?.id ?? 'custom';
301+
}
291302

292303
// When applying new filters, this is considered a major feed change,
293304
// so we clear the ad cache to get a fresh set of relevant ads.

0 commit comments

Comments
 (0)