Skip to content

Commit fd7f540

Browse files
committed
refactor(content_management): implement background pagination for dropdowns
- Add _FetchNextCountryPage and _FetchNextLanguagePage events - Implement background fetching for countries and languages - Update UI to reflect loading state for dropdown pagination - Remove inline pagination logic from EditSourceLoaded event handler
1 parent 8163f86 commit fd7f540

File tree

1 file changed

+97
-40
lines changed

1 file changed

+97
-40
lines changed

lib/content_management/bloc/edit_source/edit_source_bloc.dart

Lines changed: 97 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ import 'package:flutter/foundation.dart';
88
part 'edit_source_event.dart';
99
part 'edit_source_state.dart';
1010

11+
final class _FetchNextCountryPage extends EditSourceEvent {
12+
const _FetchNextCountryPage();
13+
}
14+
15+
final class _FetchNextLanguagePage extends EditSourceEvent {
16+
const _FetchNextLanguagePage();
17+
}
18+
1119
const _searchDebounceDuration = Duration(milliseconds: 300);
1220

1321
/// A BLoC to manage the state of editing a single source.
@@ -18,11 +26,11 @@ class EditSourceBloc extends Bloc<EditSourceEvent, EditSourceState> {
1826
required DataRepository<Country> countriesRepository,
1927
required DataRepository<Language> languagesRepository,
2028
required String sourceId,
21-
}) : _sourcesRepository = sourcesRepository,
22-
_countriesRepository = countriesRepository,
23-
_languagesRepository = languagesRepository,
24-
_sourceId = sourceId,
25-
super(const EditSourceState()) {
29+
}) : _sourcesRepository = sourcesRepository,
30+
_countriesRepository = countriesRepository,
31+
_languagesRepository = languagesRepository,
32+
_sourceId = sourceId,
33+
super(const EditSourceState()) {
2634
on<EditSourceLoaded>(_onLoaded);
2735
on<EditSourceNameChanged>(_onNameChanged);
2836
on<EditSourceDescriptionChanged>(_onDescriptionChanged);
@@ -32,6 +40,8 @@ class EditSourceBloc extends Bloc<EditSourceEvent, EditSourceState> {
3240
on<EditSourceHeadquartersChanged>(_onHeadquartersChanged);
3341
on<EditSourceStatusChanged>(_onStatusChanged);
3442
on<EditSourceSubmitted>(_onSubmitted);
43+
on<_FetchNextCountryPage>(_onFetchNextCountryPage);
44+
on<_FetchNextLanguagePage>(_onFetchNextLanguagePage);
3545
}
3646

3747
final DataRepository<Source> _sourcesRepository;
@@ -90,43 +100,13 @@ class EditSourceBloc extends Bloc<EditSourceEvent, EditSourceState> {
90100
),
91101
);
92102

93-
// After the initial page is loaded, start a background process to
103+
// After the initial page is loaded, start background processes to
94104
// fetch all remaining pages for countries and languages.
95-
//
96-
// This approach is used for the following reasons:
97-
// 1. UI Consistency: It allows us to use the standard
98-
// `DropdownButtonFormField`, which is used elsewhere in the app.
99-
// 2. Technical Limitation: The standard dropdown does not expose a
100-
// scroll controller, making on-scroll pagination impossible.
101-
//
102-
// The UI will update progressively and silently in the background as
103-
// more data arrives.
104-
while (state.countriesHasMore) {
105-
final nextCountries = await _countriesRepository.readAll(
106-
pagination: PaginationOptions(cursor: state.countriesCursor),
107-
sort: [const SortOption('name', SortOrder.asc)],
108-
);
109-
emit(
110-
state.copyWith(
111-
countries: List.of(state.countries)..addAll(nextCountries.items),
112-
countriesCursor: nextCountries.cursor,
113-
countriesHasMore: nextCountries.hasMore,
114-
),
115-
);
105+
if (state.countriesHasMore) {
106+
add(const _FetchNextCountryPage());
116107
}
117-
118-
while (state.languagesHasMore) {
119-
final nextLanguages = await _languagesRepository.readAll(
120-
pagination: PaginationOptions(cursor: state.languagesCursor),
121-
sort: [const SortOption('name', SortOrder.asc)],
122-
);
123-
emit(
124-
state.copyWith(
125-
languages: List.of(state.languages)..addAll(nextLanguages.items),
126-
languagesCursor: nextLanguages.cursor,
127-
languagesHasMore: nextLanguages.hasMore,
128-
),
129-
);
108+
if (state.languagesHasMore) {
109+
add(const _FetchNextLanguagePage());
130110
}
131111
} on HttpException catch (e) {
132112
emit(state.copyWith(status: EditSourceStatus.failure, exception: e));
@@ -214,6 +194,83 @@ class EditSourceBloc extends Bloc<EditSourceEvent, EditSourceState> {
214194
);
215195
}
216196

197+
// --- Background Data Fetching for Dropdown ---
198+
// The DropdownButtonFormField widget does not natively support on-scroll
199+
// pagination. To preserve UI consistency across the application, this BLoC
200+
// employs an event-driven background fetching mechanism.
201+
//
202+
// After the first page of items is loaded, a chain of events is initiated
203+
// to progressively fetch all remaining pages. This process is throttled
204+
// and runs in the background, ensuring the UI remains responsive while the
205+
// full list of dropdown options is populated over time.
206+
Future<void> _onFetchNextCountryPage(
207+
_FetchNextCountryPage event,
208+
Emitter<EditSourceState> emit,
209+
) async {
210+
if (!state.countriesHasMore || state.countriesIsLoadingMore) return;
211+
212+
try {
213+
emit(state.copyWith(countriesIsLoadingMore: true));
214+
215+
await Future.delayed(const Duration(milliseconds: 400));
216+
217+
final nextCountries = await _countriesRepository.readAll(
218+
pagination: PaginationOptions(cursor: state.countriesCursor),
219+
sort: [const SortOption('name', SortOrder.asc)],
220+
);
221+
222+
emit(
223+
state.copyWith(
224+
countries: List.of(state.countries)..addAll(nextCountries.items),
225+
countriesCursor: nextCountries.cursor,
226+
countriesHasMore: nextCountries.hasMore,
227+
countriesIsLoadingMore: false,
228+
),
229+
);
230+
231+
if (nextCountries.hasMore) {
232+
add(const _FetchNextCountryPage());
233+
}
234+
} catch (e) {
235+
emit(state.copyWith(countriesIsLoadingMore: false));
236+
// Optionally log the error without disrupting the user
237+
}
238+
}
239+
240+
Future<void> _onFetchNextLanguagePage(
241+
_FetchNextLanguagePage event,
242+
Emitter<EditSourceState> emit,
243+
) async {
244+
if (!state.languagesHasMore || state.languagesIsLoadingMore) return;
245+
246+
try {
247+
emit(state.copyWith(languagesIsLoadingMore: true));
248+
249+
await Future.delayed(const Duration(milliseconds: 400));
250+
251+
final nextLanguages = await _languagesRepository.readAll(
252+
pagination: PaginationOptions(cursor: state.languagesCursor),
253+
sort: [const SortOption('name', SortOrder.asc)],
254+
);
255+
256+
emit(
257+
state.copyWith(
258+
languages: List.of(state.languages)..addAll(nextLanguages.items),
259+
languagesCursor: nextLanguages.cursor,
260+
languagesHasMore: nextLanguages.hasMore,
261+
languagesIsLoadingMore: false,
262+
),
263+
);
264+
265+
if (nextLanguages.hasMore) {
266+
add(const _FetchNextLanguagePage());
267+
}
268+
} catch (e) {
269+
emit(state.copyWith(languagesIsLoadingMore: false));
270+
// Optionally log the error without disrupting the user
271+
}
272+
}
273+
217274
Future<void> _onSubmitted(
218275
EditSourceSubmitted event,
219276
Emitter<EditSourceState> emit,

0 commit comments

Comments
 (0)