Skip to content

Commit 5f10e99

Browse files
committed
refactor(content_management): simplify source creation form data handling
- Remove pagination and background fetching for countries and languages - Load full lists of countries and languages at once - Cache countries and languages in ContentManagementBloc - Update CreateSourceBloc to use cached lists - Simplify UI logic for dropdowns
1 parent e495b1f commit 5f10e99

File tree

4 files changed

+28
-206
lines changed

4 files changed

+28
-206
lines changed

lib/content_management/bloc/create_source/create_source_bloc.dart

Lines changed: 9 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,20 @@ import 'package:uuid/uuid.dart';
88
part 'create_source_event.dart';
99
part 'create_source_state.dart';
1010

11-
final class _FetchNextCountryPage extends CreateSourceEvent {
12-
const _FetchNextCountryPage();
13-
}
14-
15-
final class _FetchNextLanguagePage extends CreateSourceEvent {
16-
const _FetchNextLanguagePage();
17-
}
18-
1911
/// A BLoC to manage the state of creating a new source.
2012
class CreateSourceBloc extends Bloc<CreateSourceEvent, CreateSourceState> {
2113
/// {@macro create_source_bloc}
2214
CreateSourceBloc({
2315
required DataRepository<Source> sourcesRepository,
24-
required DataRepository<Country> countriesRepository,
25-
required DataRepository<Language> languagesRepository,
26-
}) : _sourcesRepository = sourcesRepository,
27-
_countriesRepository = countriesRepository,
28-
_languagesRepository = languagesRepository,
29-
super(const CreateSourceState()) {
30-
on<CreateSourceDataLoaded>(_onDataLoaded);
16+
required List<Country> countries,
17+
required List<Language> languages,
18+
}) : _sourcesRepository = sourcesRepository,
19+
super(
20+
CreateSourceState(
21+
countries: countries,
22+
languages: languages,
23+
),
24+
) {
3125
on<CreateSourceNameChanged>(_onNameChanged);
3226
on<CreateSourceDescriptionChanged>(_onDescriptionChanged);
3327
on<CreateSourceUrlChanged>(_onUrlChanged);
@@ -36,65 +30,11 @@ class CreateSourceBloc extends Bloc<CreateSourceEvent, CreateSourceState> {
3630
on<CreateSourceHeadquartersChanged>(_onHeadquartersChanged);
3731
on<CreateSourceStatusChanged>(_onStatusChanged);
3832
on<CreateSourceSubmitted>(_onSubmitted);
39-
on<_FetchNextCountryPage>(_onFetchNextCountryPage);
40-
on<_FetchNextLanguagePage>(_onFetchNextLanguagePage);
4133
}
4234

4335
final DataRepository<Source> _sourcesRepository;
44-
final DataRepository<Country> _countriesRepository;
45-
final DataRepository<Language> _languagesRepository;
4636
final _uuid = const Uuid();
4737

48-
Future<void> _onDataLoaded(
49-
CreateSourceDataLoaded event,
50-
Emitter<CreateSourceState> emit,
51-
) async {
52-
emit(state.copyWith(status: CreateSourceStatus.loading));
53-
try {
54-
final responses = await Future.wait([
55-
_countriesRepository.readAll(
56-
sort: [const SortOption('name', SortOrder.asc)],
57-
filter: {'status': ContentStatus.active.name},
58-
),
59-
_languagesRepository.readAll(
60-
sort: [const SortOption('name', SortOrder.asc)],
61-
filter: {'status': ContentStatus.active.name},
62-
),
63-
]);
64-
final countriesPaginated = responses[0] as PaginatedResponse<Country>;
65-
final languagesPaginated = responses[1] as PaginatedResponse<Language>;
66-
emit(
67-
state.copyWith(
68-
status: CreateSourceStatus.initial,
69-
countries: countriesPaginated.items,
70-
countriesCursor: countriesPaginated.cursor,
71-
countriesHasMore: countriesPaginated.hasMore,
72-
languages: languagesPaginated.items,
73-
languagesCursor: languagesPaginated.cursor,
74-
languagesHasMore: languagesPaginated.hasMore,
75-
),
76-
);
77-
78-
// After the initial page is loaded, start background processes to
79-
// fetch all remaining pages for countries and languages.
80-
if (state.countriesHasMore) {
81-
add(const _FetchNextCountryPage());
82-
}
83-
if (state.languagesHasMore) {
84-
add(const _FetchNextLanguagePage());
85-
}
86-
} on HttpException catch (e) {
87-
emit(state.copyWith(status: CreateSourceStatus.failure, exception: e));
88-
} catch (e) {
89-
emit(
90-
state.copyWith(
91-
status: CreateSourceStatus.failure,
92-
exception: UnknownException('An unexpected error occurred: $e'),
93-
),
94-
);
95-
}
96-
}
97-
9838
void _onNameChanged(
9939
CreateSourceNameChanged event,
10040
Emitter<CreateSourceState> emit,
@@ -150,84 +90,6 @@ class CreateSourceBloc extends Bloc<CreateSourceEvent, CreateSourceState> {
15090
}
15191

15292
// --- Background Data Fetching for Dropdown ---
153-
// The DropdownButtonFormField widget does not natively support on-scroll
154-
// pagination. To preserve UI consistency across the application, this BLoC
155-
// employs an event-driven background fetching mechanism.
156-
//
157-
// After the first page of items is loaded, a chain of events is initiated
158-
// to progressively fetch all remaining pages. This process is throttled
159-
// and runs in the background, ensuring the UI remains responsive while the
160-
// full list of dropdown options is populated over time.
161-
Future<void> _onFetchNextCountryPage(
162-
_FetchNextCountryPage event,
163-
Emitter<CreateSourceState> emit,
164-
) async {
165-
if (!state.countriesHasMore || state.countriesIsLoadingMore) return;
166-
167-
try {
168-
emit(state.copyWith(countriesIsLoadingMore: true));
169-
170-
// ignore: inference_failure_on_instance_creation
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-
// ignore: inference_failure_on_instance_creation
206-
await Future.delayed(const Duration(milliseconds: 400));
207-
208-
final nextLanguages = await _languagesRepository.readAll(
209-
pagination: PaginationOptions(cursor: state.languagesCursor),
210-
sort: [const SortOption('name', SortOrder.asc)],
211-
);
212-
213-
emit(
214-
state.copyWith(
215-
languages: List.of(state.languages)..addAll(nextLanguages.items),
216-
languagesCursor: nextLanguages.cursor,
217-
languagesHasMore: nextLanguages.hasMore,
218-
languagesIsLoadingMore: false,
219-
),
220-
);
221-
222-
if (nextLanguages.hasMore) {
223-
add(const _FetchNextLanguagePage());
224-
}
225-
} catch (e) {
226-
emit(state.copyWith(languagesIsLoadingMore: false));
227-
// Optionally log the error without disrupting the user
228-
}
229-
}
230-
23193
Future<void> _onSubmitted(
23294
CreateSourceSubmitted event,
23395
Emitter<CreateSourceState> emit,

lib/content_management/bloc/create_source/create_source_event.dart

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@ sealed class CreateSourceEvent extends Equatable {
88
List<Object?> get props => [];
99
}
1010

11-
/// Event to signal that the data for dropdowns should be loaded.
12-
final class CreateSourceDataLoaded extends CreateSourceEvent {
13-
const CreateSourceDataLoaded();
14-
}
15-
1611
/// Event for when the source's name is changed.
1712
final class CreateSourceNameChanged extends CreateSourceEvent {
1813
const CreateSourceNameChanged(this.name);

lib/content_management/bloc/create_source/create_source_state.dart

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,7 @@ final class CreateSourceState extends Equatable {
3030
this.language,
3131
this.headquarters,
3232
this.countries = const [],
33-
this.countriesHasMore = true,
34-
this.countriesIsLoadingMore = false,
35-
this.countriesCursor,
3633
this.languages = const [],
37-
this.languagesHasMore = true,
38-
this.languagesIsLoadingMore = false,
39-
this.languagesCursor,
4034
this.contentStatus = ContentStatus.active,
4135
this.exception,
4236
this.createdSource,
@@ -50,13 +44,7 @@ final class CreateSourceState extends Equatable {
5044
final Language? language;
5145
final Country? headquarters;
5246
final List<Country> countries;
53-
final bool countriesHasMore;
54-
final bool countriesIsLoadingMore;
55-
final String? countriesCursor;
5647
final List<Language> languages;
57-
final bool languagesHasMore;
58-
final bool languagesIsLoadingMore;
59-
final String? languagesCursor;
6048
final ContentStatus contentStatus;
6149
final HttpException? exception;
6250
final Source? createdSource;
@@ -79,13 +67,7 @@ final class CreateSourceState extends Equatable {
7967
ValueGetter<Language?>? language,
8068
ValueGetter<Country?>? headquarters,
8169
List<Country>? countries,
82-
bool? countriesHasMore,
83-
bool? countriesIsLoadingMore,
84-
String? countriesCursor,
8570
List<Language>? languages,
86-
bool? languagesHasMore,
87-
bool? languagesIsLoadingMore,
88-
String? languagesCursor,
8971
ContentStatus? contentStatus,
9072
HttpException? exception,
9173
Source? createdSource,
@@ -99,15 +81,7 @@ final class CreateSourceState extends Equatable {
9981
language: language != null ? language() : this.language,
10082
headquarters: headquarters != null ? headquarters() : this.headquarters,
10183
countries: countries ?? this.countries,
102-
countriesHasMore: countriesHasMore ?? this.countriesHasMore,
103-
countriesIsLoadingMore:
104-
countriesIsLoadingMore ?? this.countriesIsLoadingMore,
105-
countriesCursor: countriesCursor ?? this.countriesCursor,
10684
languages: languages ?? this.languages,
107-
languagesHasMore: languagesHasMore ?? this.languagesHasMore,
108-
languagesIsLoadingMore:
109-
languagesIsLoadingMore ?? this.languagesIsLoadingMore,
110-
languagesCursor: languagesCursor ?? this.languagesCursor,
11185
contentStatus: contentStatus ?? this.contentStatus,
11286
exception: exception,
11387
createdSource: createdSource ?? this.createdSource,
@@ -124,13 +98,7 @@ final class CreateSourceState extends Equatable {
12498
language,
12599
headquarters,
126100
countries,
127-
countriesHasMore,
128-
countriesIsLoadingMore,
129-
countriesCursor,
130101
languages,
131-
languagesHasMore,
132-
languagesIsLoadingMore,
133-
languagesCursor,
134102
contentStatus,
135103
exception,
136104
createdSource,

lib/content_management/view/create_source_page.dart

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,19 @@ class CreateSourcePage extends StatelessWidget {
2121

2222
@override
2323
Widget build(BuildContext context) {
24+
// The lists of all countries and languages are fetched once and cached in
25+
// the ContentManagementBloc. We read them here and provide them to the
26+
// CreateSourceBloc.
27+
final contentManagementState = context.read<ContentManagementBloc>().state;
28+
final allCountries = contentManagementState.allCountries;
29+
final allLanguages = contentManagementState.allLanguages;
30+
2431
return BlocProvider(
2532
create: (context) => CreateSourceBloc(
2633
sourcesRepository: context.read<DataRepository<Source>>(),
27-
countriesRepository: context.read<DataRepository<Country>>(),
28-
languagesRepository: context.read<DataRepository<Language>>(),
29-
)..add(const CreateSourceDataLoaded()),
34+
countries: allCountries,
35+
languages: allLanguages,
36+
),
3037
child: const _CreateSourceView(),
3138
);
3239
}
@@ -114,9 +121,9 @@ class _CreateSourceViewState extends State<_CreateSourceView> {
114121
if (state.status == CreateSourceStatus.failure) {
115122
return FailureStateWidget(
116123
exception: state.exception!,
117-
onRetry: () => context.read<CreateSourceBloc>().add(
118-
const CreateSourceDataLoaded(),
119-
),
124+
onRetry: () => context
125+
.read<ContentManagementBloc>()
126+
.add(const SharedDataRequested()),
120127
);
121128
}
122129

@@ -167,9 +174,6 @@ class _CreateSourceViewState extends State<_CreateSourceView> {
167174
decoration: InputDecoration(
168175
labelText: l10n.language,
169176
border: const OutlineInputBorder(),
170-
helperText: state.languagesIsLoadingMore
171-
? l10n.loadingFullList
172-
: null,
173177
),
174178
items: [
175179
DropdownMenuItem(value: null, child: Text(l10n.none)),
@@ -180,11 +184,9 @@ class _CreateSourceViewState extends State<_CreateSourceView> {
180184
),
181185
),
182186
],
183-
onChanged: state.languagesIsLoadingMore
184-
? null
185-
: (value) => context.read<CreateSourceBloc>().add(
186-
CreateSourceLanguageChanged(value),
187-
),
187+
onChanged: (value) => context
188+
.read<CreateSourceBloc>()
189+
.add(CreateSourceLanguageChanged(value)),
188190
),
189191
const SizedBox(height: AppSpacing.lg),
190192
DropdownButtonFormField<SourceType?>(
@@ -212,9 +214,6 @@ class _CreateSourceViewState extends State<_CreateSourceView> {
212214
decoration: InputDecoration(
213215
labelText: l10n.headquarters,
214216
border: const OutlineInputBorder(),
215-
helperText: state.countriesIsLoadingMore
216-
? l10n.loadingFullList
217-
: null,
218217
),
219218
items: [
220219
DropdownMenuItem(value: null, child: Text(l10n.none)),
@@ -241,11 +240,9 @@ class _CreateSourceViewState extends State<_CreateSourceView> {
241240
),
242241
),
243242
],
244-
onChanged: state.countriesIsLoadingMore
245-
? null
246-
: (value) => context.read<CreateSourceBloc>().add(
247-
CreateSourceHeadquartersChanged(value),
248-
),
243+
onChanged: (value) => context
244+
.read<CreateSourceBloc>()
245+
.add(CreateSourceHeadquartersChanged(value)),
249246
),
250247
const SizedBox(height: AppSpacing.lg),
251248
DropdownButtonFormField<ContentStatus>(

0 commit comments

Comments
 (0)