Skip to content

Enhance contenet management data perfetching performance #55

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
8 changes: 5 additions & 3 deletions lib/app/view/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,14 @@ class App extends StatelessWidget {
headlinesRepository: context.read<DataRepository<Headline>>(),
topicsRepository: context.read<DataRepository<Topic>>(),
sourcesRepository: context.read<DataRepository<Source>>(),
),
countriesRepository: context.read<DataRepository<Country>>(),
languagesRepository: context.read<DataRepository<Language>>(),
)..add(const SharedDataRequested()),
),
BlocProvider(
create: (context) => DashboardBloc(
dashboardSummaryRepository:
context.read<DataRepository<DashboardSummary>>(),
dashboardSummaryRepository: context
.read<DataRepository<DashboardSummary>>(),
headlinesRepository: context.read<DataRepository<Headline>>(),
topicsRepository: context.read<DataRepository<Topic>>(),
sourcesRepository: context.read<DataRepository<Source>>(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ class ArchivedHeadlinesBloc
extends Bloc<ArchivedHeadlinesEvent, ArchivedHeadlinesState> {
ArchivedHeadlinesBloc({
required DataRepository<Headline> headlinesRepository,
}) : _headlinesRepository = headlinesRepository,
super(const ArchivedHeadlinesState()) {
}) : _headlinesRepository = headlinesRepository,
super(const ArchivedHeadlinesState()) {
on<LoadArchivedHeadlinesRequested>(_onLoadArchivedHeadlinesRequested);
on<RestoreHeadlineRequested>(_onRestoreHeadlineRequested);
on<DeleteHeadlineForeverRequested>(_onDeleteHeadlineForeverRequested);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ class ArchivedHeadlinesState extends Equatable {

@override
List<Object?> get props => [
status,
headlines,
cursor,
hasMore,
exception,
restoredHeadline,
];
status,
headlines,
cursor,
hasMore,
exception,
restoredHeadline,
];
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ class ArchivedSourcesBloc
extends Bloc<ArchivedSourcesEvent, ArchivedSourcesState> {
ArchivedSourcesBloc({
required DataRepository<Source> sourcesRepository,
}) : _sourcesRepository = sourcesRepository,
super(const ArchivedSourcesState()) {
}) : _sourcesRepository = sourcesRepository,
super(const ArchivedSourcesState()) {
on<LoadArchivedSourcesRequested>(_onLoadArchivedSourcesRequested);
on<RestoreSourceRequested>(_onRestoreSourceRequested);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ class ArchivedSourcesState extends Equatable {

@override
List<Object?> get props => [
status,
sources,
cursor,
hasMore,
exception,
restoredSource,
];
status,
sources,
cursor,
hasMore,
exception,
restoredSource,
];
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ class ArchivedTopicsBloc
extends Bloc<ArchivedTopicsEvent, ArchivedTopicsState> {
ArchivedTopicsBloc({
required DataRepository<Topic> topicsRepository,
}) : _topicsRepository = topicsRepository,
super(const ArchivedTopicsState()) {
}) : _topicsRepository = topicsRepository,
super(const ArchivedTopicsState()) {
on<LoadArchivedTopicsRequested>(_onLoadArchivedTopicsRequested);
on<RestoreTopicRequested>(_onRestoreTopicRequested);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ class ArchivedTopicsState extends Equatable {

@override
List<Object?> get props => [
status,
topics,
cursor,
hasMore,
exception,
restoredTopic,
];
status,
topics,
cursor,
hasMore,
exception,
restoredTopic,
];
}
98 changes: 98 additions & 0 deletions lib/content_management/bloc/content_management_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,15 @@ class ContentManagementBloc
required DataRepository<Headline> headlinesRepository,
required DataRepository<Topic> topicsRepository,
required DataRepository<Source> sourcesRepository,
required DataRepository<Country> countriesRepository,
required DataRepository<Language> languagesRepository,
}) : _headlinesRepository = headlinesRepository,
_topicsRepository = topicsRepository,
_sourcesRepository = sourcesRepository,
_countriesRepository = countriesRepository,
_languagesRepository = languagesRepository,
super(const ContentManagementState()) {
on<SharedDataRequested>(_onSharedDataRequested);
on<ContentManagementTabChanged>(_onContentManagementTabChanged);
on<LoadHeadlinesRequested>(_onLoadHeadlinesRequested);
on<HeadlineUpdated>(_onHeadlineUpdated);
Expand All @@ -43,6 +48,99 @@ class ContentManagementBloc
final DataRepository<Headline> _headlinesRepository;
final DataRepository<Topic> _topicsRepository;
final DataRepository<Source> _sourcesRepository;
final DataRepository<Country> _countriesRepository;
final DataRepository<Language> _languagesRepository;

// --- Background Data Fetching for countries/languages for the ui Dropdown ---
//
// The DropdownButtonFormField widget does not natively support on-scroll
// pagination. To preserve UI consistency across the application, this BLoC
// employs an event-driven background fetching mechanism.
Future<void> _onSharedDataRequested(
SharedDataRequested event,
Emitter<ContentManagementState> emit,
) async {
// Helper function to fetch all items of a given type.
Future<List<T>> fetchAll<T>({
required DataRepository<T> repository,
required List<SortOption> sort,
}) async {
final allItems = <T>[];
String? cursor;
bool hasMore;

do {
final response = await repository.readAll(
sort: sort,
pagination: PaginationOptions(cursor: cursor),
filter: {'status': ContentStatus.active.name},
);
allItems.addAll(response.items);
cursor = response.cursor;
hasMore = response.hasMore;
} while (hasMore);

return allItems;
}

// Check if data is already loaded or is currently loading to prevent
// redundant fetches.
if (state.allCountriesStatus == ContentManagementStatus.success &&
state.allLanguagesStatus == ContentManagementStatus.success) {
return;
}

// Set loading status for both lists.
emit(
state.copyWith(
allCountriesStatus: ContentManagementStatus.loading,
allLanguagesStatus: ContentManagementStatus.loading,
),
);

try {
// Fetch both lists in parallel.
final results = await Future.wait([
fetchAll<Country>(
repository: _countriesRepository,
sort: [const SortOption('name', SortOrder.asc)],
),
fetchAll<Language>(
repository: _languagesRepository,
sort: [const SortOption('name', SortOrder.asc)],
),
]);

final countries = results[0] as List<Country>;
final languages = results[1] as List<Language>;

// Update the state with the complete lists.
emit(
state.copyWith(
allCountries: countries,
allCountriesStatus: ContentManagementStatus.success,
allLanguages: languages,
allLanguagesStatus: ContentManagementStatus.success,
),
);
} on HttpException catch (e) {
emit(
state.copyWith(
allCountriesStatus: ContentManagementStatus.failure,
allLanguagesStatus: ContentManagementStatus.failure,
exception: e,
),
);
} catch (e) {
emit(
state.copyWith(
allCountriesStatus: ContentManagementStatus.failure,
allLanguagesStatus: ContentManagementStatus.failure,
exception: UnknownException('An unexpected error occurred: $e'),
),
);
}
}

void _onContentManagementTabChanged(
ContentManagementTabChanged event,
Expand Down
9 changes: 9 additions & 0 deletions lib/content_management/bloc/content_management_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,12 @@ final class SourceUpdated extends ContentManagementEvent {
@override
List<Object?> get props => [source];
}

/// {@template shared_data_requested}
/// Event to request loading of shared data like countries and languages.
/// This should be dispatched once when the content management section is loaded.
/// {@endtemplate}
final class SharedDataRequested extends ContentManagementEvent {
/// {@macro shared_data_requested}
const SharedDataRequested();
}
28 changes: 28 additions & 0 deletions lib/content_management/bloc/content_management_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ class ContentManagementState extends Equatable {
this.sources = const [],
this.sourcesCursor,
this.sourcesHasMore = false,
this.allCountriesStatus = ContentManagementStatus.initial,
this.allCountries = const [],
this.allLanguagesStatus = ContentManagementStatus.initial,
this.allLanguages = const [],
this.exception,
});

Expand Down Expand Up @@ -74,6 +78,18 @@ class ContentManagementState extends Equatable {
/// Indicates if there are more sources to load.
final bool sourcesHasMore;

/// Status of all countries data operations.
final ContentManagementStatus allCountriesStatus;

/// Cached list of all countries.
final List<Country> allCountries;

/// Status of all languages data operations.
final ContentManagementStatus allLanguagesStatus;

/// Cached list of all languages.
final List<Language> allLanguages;

/// The error describing an operation failure, if any.
final HttpException? exception;

Expand All @@ -92,6 +108,10 @@ class ContentManagementState extends Equatable {
List<Source>? sources,
String? sourcesCursor,
bool? sourcesHasMore,
ContentManagementStatus? allCountriesStatus,
List<Country>? allCountries,
ContentManagementStatus? allLanguagesStatus,
List<Language>? allLanguages,
HttpException? exception,
}) {
return ContentManagementState(
Expand All @@ -108,6 +128,10 @@ class ContentManagementState extends Equatable {
sources: sources ?? this.sources,
sourcesCursor: sourcesCursor ?? this.sourcesCursor,
sourcesHasMore: sourcesHasMore ?? this.sourcesHasMore,
allCountriesStatus: allCountriesStatus ?? this.allCountriesStatus,
allCountries: allCountries ?? this.allCountries,
allLanguagesStatus: allLanguagesStatus ?? this.allLanguagesStatus,
allLanguages: allLanguages ?? this.allLanguages,
exception: exception ?? this.exception,
);
}
Expand All @@ -127,6 +151,10 @@ class ContentManagementState extends Equatable {
sources,
sourcesCursor,
sourcesHasMore,
allCountriesStatus,
allCountries,
allLanguagesStatus,
allLanguages,
exception,
];
}
Loading