1+ import 'dart:async' ;
2+
13import 'package:bloc/bloc.dart' ;
24import 'package:core/core.dart' ;
35import 'package:data_repository/data_repository.dart' ;
46import 'package:equatable/equatable.dart' ;
5- import 'package:flutter_news_app_web_dashboard_full_source_code/shared/services/throttled_fetching_service .dart' ;
7+ import 'package:ui_kit/ui_kit .dart' ;
68
79part 'content_management_event.dart' ;
810part 'content_management_state.dart' ;
@@ -25,117 +27,51 @@ class ContentManagementBloc
2527 required DataRepository <Headline > headlinesRepository,
2628 required DataRepository <Topic > topicsRepository,
2729 required DataRepository <Source > sourcesRepository,
28- required DataRepository <Country > countriesRepository,
29- required DataRepository <Language > languagesRepository,
30- required ThrottledFetchingService fetchingService,
3130 }) : _headlinesRepository = headlinesRepository,
3231 _topicsRepository = topicsRepository,
3332 _sourcesRepository = sourcesRepository,
34- _countriesRepository = countriesRepository,
35- _languagesRepository = languagesRepository,
36- _fetchingService = fetchingService,
3733 super (const ContentManagementState ()) {
38- on < SharedDataRequested > (_onSharedDataRequested);
3934 on < ContentManagementTabChanged > (_onContentManagementTabChanged);
4035 on < LoadHeadlinesRequested > (_onLoadHeadlinesRequested);
41- on < HeadlineUpdated > (_onHeadlineUpdated);
4236 on < ArchiveHeadlineRequested > (_onArchiveHeadlineRequested);
4337 on < LoadTopicsRequested > (_onLoadTopicsRequested);
44- on < TopicUpdated > (_onTopicUpdated);
4538 on < ArchiveTopicRequested > (_onArchiveTopicRequested);
4639 on < LoadSourcesRequested > (_onLoadSourcesRequested);
47- on < SourceUpdated > (_onSourceUpdated);
4840 on < ArchiveSourceRequested > (_onArchiveSourceRequested);
41+
42+ _headlineUpdateSubscription = _headlinesRepository.entityUpdated
43+ .where ((type) => type == Headline )
44+ .listen ((_) {
45+ add (const LoadHeadlinesRequested (limit: kDefaultRowsPerPage));
46+ });
47+
48+ _topicUpdateSubscription = _topicsRepository.entityUpdated
49+ .where ((type) => type == Topic )
50+ .listen ((_) {
51+ add (const LoadTopicsRequested (limit: kDefaultRowsPerPage));
52+ });
53+
54+ _sourceUpdateSubscription = _sourcesRepository.entityUpdated
55+ .where ((type) => type == Source )
56+ .listen ((_) {
57+ add (const LoadSourcesRequested (limit: kDefaultRowsPerPage));
58+ });
4959 }
5060
5161 final DataRepository <Headline > _headlinesRepository;
5262 final DataRepository <Topic > _topicsRepository;
5363 final DataRepository <Source > _sourcesRepository;
54- final DataRepository <Country > _countriesRepository;
55- final DataRepository <Language > _languagesRepository;
56- final ThrottledFetchingService _fetchingService;
57-
58- /// Handles the pre-fetching of shared data required for the content
59- /// management section.
60- ///
61- /// **Strategy Rationale (The "Why"):**
62- /// This pre-fetching strategy is a direct result of a UI component choice
63- /// made to preserve visual consistency across the application. The standard
64- /// `DropdownButtonFormField` is used for selection fields in forms.
65- /// A key limitation of this widget is its lack of native support for
66- /// on-scroll pagination or dynamic data loading.
67- ///
68- /// To work around this, and to ensure a seamless user experience without
69- /// loading delays when a form is opened, we must load the entire dataset
70- /// for these dropdowns (e.g., all countries, all languages) into the state
71- /// ahead of time.
72- ///
73- /// **Implementation (The "How"):**
74- /// To execute this pre-fetch efficiently, this handler utilizes the
75- /// `ThrottledFetchingService` . This service fetches all pages of a given
76- /// resource in parallel, which dramatically reduces the load time compared
77- /// to fetching them sequentially, making the upfront data load manageable.
78- Future <void > _onSharedDataRequested (
79- SharedDataRequested event,
80- Emitter <ContentManagementState > emit,
81- ) async {
82- // Check if data is already loaded or is currently loading to prevent
83- // redundant fetches.
84- if (state.allCountriesStatus == ContentManagementStatus .success &&
85- state.allLanguagesStatus == ContentManagementStatus .success) {
86- return ;
87- }
88-
89- // Set loading status for both lists.
90- emit (
91- state.copyWith (
92- allCountriesStatus: ContentManagementStatus .loading,
93- allLanguagesStatus: ContentManagementStatus .loading,
94- ),
95- );
96-
97- try {
98- // Fetch both lists in parallel using the dedicated fetching service.
99- final results = await Future .wait ([
100- _fetchingService.fetchAll <Country >(
101- repository: _countriesRepository,
102- sort: [const SortOption ('name' , SortOrder .asc)],
103- ),
104- _fetchingService.fetchAll <Language >(
105- repository: _languagesRepository,
106- sort: [const SortOption ('name' , SortOrder .asc)],
107- ),
108- ]);
10964
110- final countries = results[0 ] as List <Country >;
111- final languages = results[1 ] as List <Language >;
65+ late final StreamSubscription <Type > _headlineUpdateSubscription;
66+ late final StreamSubscription <Type > _topicUpdateSubscription;
67+ late final StreamSubscription <Type > _sourceUpdateSubscription;
11268
113- // Update the state with the complete lists.
114- emit (
115- state.copyWith (
116- allCountries: countries,
117- allCountriesStatus: ContentManagementStatus .success,
118- allLanguages: languages,
119- allLanguagesStatus: ContentManagementStatus .success,
120- ),
121- );
122- } on HttpException catch (e) {
123- emit (
124- state.copyWith (
125- allCountriesStatus: ContentManagementStatus .failure,
126- allLanguagesStatus: ContentManagementStatus .failure,
127- exception: e,
128- ),
129- );
130- } catch (e) {
131- emit (
132- state.copyWith (
133- allCountriesStatus: ContentManagementStatus .failure,
134- allLanguagesStatus: ContentManagementStatus .failure,
135- exception: UnknownException ('An unexpected error occurred: $e ' ),
136- ),
137- );
138- }
69+ @override
70+ Future <void > close () {
71+ _headlineUpdateSubscription.cancel ();
72+ _topicUpdateSubscription.cancel ();
73+ _sourceUpdateSubscription.cancel ();
74+ return super .close ();
13975 }
14076
14177 void _onContentManagementTabChanged (
@@ -226,18 +162,6 @@ class ContentManagementBloc
226162 }
227163 }
228164
229- void _onHeadlineUpdated (
230- HeadlineUpdated event,
231- Emitter <ContentManagementState > emit,
232- ) {
233- final updatedHeadlines = List <Headline >.from (state.headlines);
234- final index = updatedHeadlines.indexWhere ((h) => h.id == event.headline.id);
235- if (index != - 1 ) {
236- updatedHeadlines[index] = event.headline;
237- emit (state.copyWith (headlines: updatedHeadlines));
238- }
239- }
240-
241165 Future <void > _onLoadTopicsRequested (
242166 LoadTopicsRequested event,
243167 Emitter <ContentManagementState > emit,
@@ -319,18 +243,6 @@ class ContentManagementBloc
319243 }
320244 }
321245
322- void _onTopicUpdated (
323- TopicUpdated event,
324- Emitter <ContentManagementState > emit,
325- ) {
326- final updatedTopics = List <Topic >.from (state.topics);
327- final index = updatedTopics.indexWhere ((t) => t.id == event.topic.id);
328- if (index != - 1 ) {
329- updatedTopics[index] = event.topic;
330- emit (state.copyWith (topics: updatedTopics));
331- }
332- }
333-
334246 Future <void > _onLoadSourcesRequested (
335247 LoadSourcesRequested event,
336248 Emitter <ContentManagementState > emit,
@@ -411,16 +323,4 @@ class ContentManagementBloc
411323 );
412324 }
413325 }
414-
415- void _onSourceUpdated (
416- SourceUpdated event,
417- Emitter <ContentManagementState > emit,
418- ) {
419- final updatedSources = List <Source >.from (state.sources);
420- final index = updatedSources.indexWhere ((s) => s.id == event.source.id);
421- if (index != - 1 ) {
422- updatedSources[index] = event.source;
423- emit (state.copyWith (sources: updatedSources));
424- }
425- }
426326}
0 commit comments