Skip to content

Commit 1022797

Browse files
committed
fix(headlines-feed): resolve race condition in filter saving flow
- Prevent race condition in filter saving process - Avoid navigation stack error and black screen after saving filter - Coordinate navigation actions to ensure proper page transitions - Refactor filter saving logic for more robust asynchronous handling
1 parent 00ff81e commit 1022797

File tree

2 files changed

+36
-7
lines changed

2 files changed

+36
-7
lines changed

lib/headlines-feed/view/headlines_filter_page.dart

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,36 +135,56 @@ class _HeadlinesFilterViewState extends State<_HeadlinesFilterView> {
135135

136136
/// Initiates the process of saving a filter by showing the naming dialog,
137137
/// and then applies it.
138-
void _saveAndApplyFilter() {
138+
///
139+
/// This function `await`s the result of the `SaveFilterDialog`. This is
140+
/// crucial to prevent a race condition where the `HeadlinesFilterPage` might
141+
/// be popped before the dialog is fully dismissed, which was causing a
142+
/// navigation stack error and a black screen.
143+
Future<void> _saveAndApplyFilter() async {
139144
final filterState = context.read<HeadlinesFilterBloc>().state;
140-
showDialog<void>(
145+
146+
// `showDialog` returns a Future that completes when the dialog is popped.
147+
// We await its result (`true` on successful save) to synchronize navigation.
148+
final didSave = await showDialog<bool>(
141149
context: context,
142150
builder: (_) {
143151
return SaveFilterDialog(
144152
onSave: (name) {
153+
// This callback is executed when the user submits the save dialog.
145154
final newFilter = SavedFilter(
146155
id: const Uuid().v4(),
147156
name: name,
148157
topics: filterState.selectedTopics.toList(),
149158
sources: filterState.selectedSources.toList(),
150159
countries: filterState.selectedCountries.toList(),
151160
);
152-
// Add the filter to the global state.
161+
// Add the new filter to the global AppBloc state.
153162
context.read<AppBloc>().add(SavedFilterAdded(filter: newFilter));
154-
// Apply the newly saved filter and exit the page.
155-
_applyAndExit(newFilter);
163+
164+
// Apply the filter to the HeadlinesFeedBloc. The page is not
165+
// popped here; that action is deferred until after the dialog
166+
// has been successfully dismissed.
167+
_applyFilter(newFilter);
156168
},
157169
);
158170
},
159171
);
172+
173+
// After the dialog is popped and we have the result, check if the save
174+
// was successful and if the widget is still in the tree.
175+
// This check prevents the "Don't use 'BuildContext's across async gaps"
176+
// lint warning and ensures we don't try to pop a disposed context.
177+
if (didSave == true && mounted) {
178+
context.pop();
179+
}
160180
}
161181

162182
/// Applies the current filter selections to the feed and pops the page.
163183
///
164184
/// If a [savedFilter] is provided, it's passed to the event to ensure
165185
/// its chip is correctly selected on the feed. Otherwise, the filter is
166186
/// treated as a "custom" one.
167-
void _applyAndExit(SavedFilter? savedFilter) {
187+
void _applyFilter(SavedFilter? savedFilter) {
168188
final filterState = context.read<HeadlinesFilterBloc>().state;
169189
final newFilter = HeadlineFilter(
170190
topics: filterState.selectedTopics.toList(),
@@ -179,6 +199,12 @@ class _HeadlinesFilterViewState extends State<_HeadlinesFilterView> {
179199
adThemeStyle: AdThemeStyle.fromTheme(Theme.of(context)),
180200
),
181201
);
202+
}
203+
204+
void _applyAndExit(SavedFilter? savedFilter) {
205+
// This helper method now separates applying the filter from exiting.
206+
// It's called for the "Apply Only" flow.
207+
_applyFilter(savedFilter);
182208
context.pop();
183209
}
184210

lib/headlines-feed/widgets/save_filter_dialog.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ class _SaveFilterDialogState extends State<SaveFilterDialog> {
4444
void _submitForm() {
4545
if (_formKey.currentState!.validate()) {
4646
widget.onSave(_controller.text.trim());
47-
Navigator.of(context).pop();
47+
// Pop the dialog and return `true` to signal to the caller that the
48+
// save operation was successfully initiated. This allows the caller
49+
// to coordinate subsequent navigation actions, preventing race conditions.
50+
Navigator.of(context).pop(true); // Return true on success.
4851
}
4952
}
5053

0 commit comments

Comments
 (0)