Skip to content

Commit c3fbeb6

Browse files
committed
refactor(content_management): implement background pagination for countries dropdown
- Add _FetchNextCountryPage event to handle asynchronous data fetching - Modify _onDataLoaded to trigger background fetching - Implement throttling and state management for ongoing requests - Update documentation to explain the new approach
1 parent 80a32ed commit c3fbeb6

File tree

1 file changed

+55
-25
lines changed

1 file changed

+55
-25
lines changed

lib/content_management/bloc/create_headline/create_headline_bloc.dart

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import 'package:uuid/uuid.dart';
99
part 'create_headline_event.dart';
1010
part 'create_headline_state.dart';
1111

12+
final class _FetchNextCountryPage extends CreateHeadlineEvent {
13+
const _FetchNextCountryPage();
14+
}
15+
1216
const _searchDebounceDuration = Duration(milliseconds: 300);
1317

1418
/// A BLoC to manage the state of creating a new headline.
@@ -20,11 +24,11 @@ class CreateHeadlineBloc
2024
required DataRepository<Source> sourcesRepository,
2125
required DataRepository<Topic> topicsRepository,
2226
required DataRepository<Country> countriesRepository,
23-
}) : _headlinesRepository = headlinesRepository,
24-
_sourcesRepository = sourcesRepository,
25-
_topicsRepository = topicsRepository,
26-
_countriesRepository = countriesRepository,
27-
super(const CreateHeadlineState()) {
27+
}) : _headlinesRepository = headlinesRepository,
28+
_sourcesRepository = sourcesRepository,
29+
_topicsRepository = topicsRepository,
30+
_countriesRepository = countriesRepository,
31+
super(const CreateHeadlineState()) {
2832
on<CreateHeadlineDataLoaded>(_onDataLoaded);
2933
on<CreateHeadlineTitleChanged>(_onTitleChanged);
3034
on<CreateHeadlineExcerptChanged>(_onExcerptChanged);
@@ -35,6 +39,7 @@ class CreateHeadlineBloc
3539
on<CreateHeadlineCountryChanged>(_onCountryChanged);
3640
on<CreateHeadlineStatusChanged>(_onStatusChanged);
3741
on<CreateHeadlineSubmitted>(_onSubmitted);
42+
on<_FetchNextCountryPage>(_onFetchNextCountryPage);
3843
}
3944

4045
final DataRepository<Headline> _headlinesRepository;
@@ -78,26 +83,8 @@ class CreateHeadlineBloc
7883

7984
// After the initial page of countries is loaded, start a background
8085
// process to fetch all remaining pages.
81-
//
82-
// This approach is used for the following reasons:
83-
// 1. UI Consistency: It allows us to use the standard
84-
// `DropdownButtonFormField`, which is used elsewhere in the app.
85-
// 2. Technical Limitation: The standard dropdown does not expose a
86-
//
87-
// The UI will update progressively and silently in the background as
88-
// more data arrives.
89-
while (state.countriesHasMore) {
90-
final nextCountries = await _countriesRepository.readAll(
91-
pagination: PaginationOptions(cursor: state.countriesCursor),
92-
sort: [const SortOption('name', SortOrder.asc)],
93-
);
94-
emit(
95-
state.copyWith(
96-
countries: List.of(state.countries)..addAll(nextCountries.items),
97-
countriesCursor: nextCountries.cursor,
98-
countriesHasMore: nextCountries.hasMore,
99-
),
100-
);
86+
if (state.countriesHasMore) {
87+
add(const _FetchNextCountryPage());
10188
}
10289
} on HttpException catch (e) {
10390
emit(state.copyWith(status: CreateHeadlineStatus.failure, exception: e));
@@ -172,6 +159,49 @@ class CreateHeadlineBloc
172159
);
173160
}
174161

162+
// --- Background Data Fetching for Dropdown ---
163+
// The DropdownButtonFormField widget does not natively support on-scroll
164+
// pagination. To preserve UI consistency across the application, this BLoC
165+
// employs an event-driven background fetching mechanism.
166+
//
167+
// After the first page of items is loaded, a chain of events is initiated
168+
// to progressively fetch all remaining pages. This process is throttled
169+
// and runs in the background, ensuring the UI remains responsive while the
170+
// full list of dropdown options is populated over time.
171+
Future<void> _onFetchNextCountryPage(
172+
_FetchNextCountryPage event,
173+
Emitter<CreateHeadlineState> emit,
174+
) async {
175+
if (!state.countriesHasMore || state.countriesIsLoadingMore) return;
176+
177+
try {
178+
emit(state.copyWith(countriesIsLoadingMore: true));
179+
180+
await Future.delayed(const Duration(milliseconds: 400));
181+
182+
final nextCountries = await _countriesRepository.readAll(
183+
pagination: PaginationOptions(cursor: state.countriesCursor),
184+
sort: [const SortOption('name', SortOrder.asc)],
185+
);
186+
187+
emit(
188+
state.copyWith(
189+
countries: List.of(state.countries)..addAll(nextCountries.items),
190+
countriesCursor: nextCountries.cursor,
191+
countriesHasMore: nextCountries.hasMore,
192+
countriesIsLoadingMore: false,
193+
),
194+
);
195+
196+
if (nextCountries.hasMore) {
197+
add(const _FetchNextCountryPage());
198+
}
199+
} catch (e) {
200+
emit(state.copyWith(countriesIsLoadingMore: false));
201+
// Optionally log the error without disrupting the user
202+
}
203+
}
204+
175205
Future<void> _onSubmitted(
176206
CreateHeadlineSubmitted event,
177207
Emitter<CreateHeadlineState> emit,

0 commit comments

Comments
 (0)