@@ -9,6 +9,14 @@ import 'package:uuid/uuid.dart';
99part 'create_source_event.dart' ;
1010part 'create_source_state.dart' ;
1111
12+ final class _FetchNextCountryPage extends CreateSourceEvent {
13+ const _FetchNextCountryPage ();
14+ }
15+
16+ final class _FetchNextLanguagePage extends CreateSourceEvent {
17+ const _FetchNextLanguagePage ();
18+ }
19+
1220const _searchDebounceDuration = Duration (milliseconds: 300 );
1321
1422/// A BLoC to manage the state of creating a new source.
@@ -18,10 +26,10 @@ class CreateSourceBloc extends Bloc<CreateSourceEvent, CreateSourceState> {
1826 required DataRepository <Source > sourcesRepository,
1927 required DataRepository <Country > countriesRepository,
2028 required DataRepository <Language > languagesRepository,
21- }) : _sourcesRepository = sourcesRepository,
22- _countriesRepository = countriesRepository,
23- _languagesRepository = languagesRepository,
24- super (const CreateSourceState ()) {
29+ }) : _sourcesRepository = sourcesRepository,
30+ _countriesRepository = countriesRepository,
31+ _languagesRepository = languagesRepository,
32+ super (const CreateSourceState ()) {
2533 on < CreateSourceDataLoaded > (_onDataLoaded);
2634 on < CreateSourceNameChanged > (_onNameChanged);
2735 on < CreateSourceDescriptionChanged > (_onDescriptionChanged);
@@ -31,6 +39,8 @@ class CreateSourceBloc extends Bloc<CreateSourceEvent, CreateSourceState> {
3139 on < CreateSourceHeadquartersChanged > (_onHeadquartersChanged);
3240 on < CreateSourceStatusChanged > (_onStatusChanged);
3341 on < CreateSourceSubmitted > (_onSubmitted);
42+ on < _FetchNextCountryPage > (_onFetchNextCountryPage);
43+ on < _FetchNextLanguagePage > (_onFetchNextLanguagePage);
3444 }
3545
3646 final DataRepository <Source > _sourcesRepository;
@@ -66,43 +76,13 @@ class CreateSourceBloc extends Bloc<CreateSourceEvent, CreateSourceState> {
6676 ),
6777 );
6878
69- // After the initial page is loaded, start a background process to
79+ // After the initial page is loaded, start background processes to
7080 // fetch all remaining pages for countries and languages.
71- //
72- // This approach is used for the following reasons:
73- // 1. UI Consistency: It allows us to use the standard
74- // `DropdownButtonFormField`, which is used elsewhere in the app.
75- // 2. Technical Limitation: The standard dropdown does not expose a
76- // scroll controller, making on-scroll pagination impossible.
77- //
78- // The UI will update progressively and silently in the background as
79- // more data arrives.
80- while (state.countriesHasMore) {
81- final nextCountries = await _countriesRepository.readAll (
82- pagination: PaginationOptions (cursor: state.countriesCursor),
83- sort: [const SortOption ('name' , SortOrder .asc)],
84- );
85- emit (
86- state.copyWith (
87- countries: List .of (state.countries)..addAll (nextCountries.items),
88- countriesCursor: nextCountries.cursor,
89- countriesHasMore: nextCountries.hasMore,
90- ),
91- );
81+ if (state.countriesHasMore) {
82+ add (const _FetchNextCountryPage ());
9283 }
93-
94- while (state.languagesHasMore) {
95- final nextLanguages = await _languagesRepository.readAll (
96- pagination: PaginationOptions (cursor: state.languagesCursor),
97- sort: [const SortOption ('name' , SortOrder .asc)],
98- );
99- emit (
100- state.copyWith (
101- languages: List .of (state.languages)..addAll (nextLanguages.items),
102- languagesCursor: nextLanguages.cursor,
103- languagesHasMore: nextLanguages.hasMore,
104- ),
105- );
84+ if (state.languagesHasMore) {
85+ add (const _FetchNextLanguagePage ());
10686 }
10787 } on HttpException catch (e) {
10888 emit (state.copyWith (status: CreateSourceStatus .failure, exception: e));
@@ -170,6 +150,83 @@ class CreateSourceBloc extends Bloc<CreateSourceEvent, CreateSourceState> {
170150 );
171151 }
172152
153+ // --- Background Data Fetching for Dropdown ---
154+ // The DropdownButtonFormField widget does not natively support on-scroll
155+ // pagination. To preserve UI consistency across the application, this BLoC
156+ // employs an event-driven background fetching mechanism.
157+ //
158+ // After the first page of items is loaded, a chain of events is initiated
159+ // to progressively fetch all remaining pages. This process is throttled
160+ // and runs in the background, ensuring the UI remains responsive while the
161+ // full list of dropdown options is populated over time.
162+ Future <void > _onFetchNextCountryPage (
163+ _FetchNextCountryPage event,
164+ Emitter <CreateSourceState > emit,
165+ ) async {
166+ if (! state.countriesHasMore || state.countriesIsLoadingMore) return ;
167+
168+ try {
169+ emit (state.copyWith (countriesIsLoadingMore: true ));
170+
171+ await Future .delayed (const Duration (milliseconds: 400 ));
172+
173+ final nextCountries = await _countriesRepository.readAll (
174+ pagination: PaginationOptions (cursor: state.countriesCursor),
175+ sort: [const SortOption ('name' , SortOrder .asc)],
176+ );
177+
178+ emit (
179+ state.copyWith (
180+ countries: List .of (state.countries)..addAll (nextCountries.items),
181+ countriesCursor: nextCountries.cursor,
182+ countriesHasMore: nextCountries.hasMore,
183+ countriesIsLoadingMore: false ,
184+ ),
185+ );
186+
187+ if (nextCountries.hasMore) {
188+ add (const _FetchNextCountryPage ());
189+ }
190+ } catch (e) {
191+ emit (state.copyWith (countriesIsLoadingMore: false ));
192+ // Optionally log the error without disrupting the user
193+ }
194+ }
195+
196+ Future <void > _onFetchNextLanguagePage (
197+ _FetchNextLanguagePage event,
198+ Emitter <CreateSourceState > emit,
199+ ) async {
200+ if (! state.languagesHasMore || state.languagesIsLoadingMore) return ;
201+
202+ try {
203+ emit (state.copyWith (languagesIsLoadingMore: true ));
204+
205+ await Future .delayed (const Duration (milliseconds: 400 ));
206+
207+ final nextLanguages = await _languagesRepository.readAll (
208+ pagination: PaginationOptions (cursor: state.languagesCursor),
209+ sort: [const SortOption ('name' , SortOrder .asc)],
210+ );
211+
212+ emit (
213+ state.copyWith (
214+ languages: List .of (state.languages)..addAll (nextLanguages.items),
215+ languagesCursor: nextLanguages.cursor,
216+ languagesHasMore: nextLanguages.hasMore,
217+ languagesIsLoadingMore: false ,
218+ ),
219+ );
220+
221+ if (nextLanguages.hasMore) {
222+ add (const _FetchNextLanguagePage ());
223+ }
224+ } catch (e) {
225+ emit (state.copyWith (languagesIsLoadingMore: false ));
226+ // Optionally log the error without disrupting the user
227+ }
228+ }
229+
173230 Future <void > _onSubmitted (
174231 CreateSourceSubmitted event,
175232 Emitter <CreateSourceState > emit,
0 commit comments