Skip to content

Commit f266df8

Browse files
authored
Merge pull request #44 from flutter-news-app-full-source-code/fix-content-management-malfunxtionned-countries-&-languages-dropdown
Fix content management malfunxtionned countries & languages dropdown
2 parents 1b13a0f + 332cce0 commit f266df8

File tree

5 files changed

+94
-55
lines changed

5 files changed

+94
-55
lines changed

lib/content_management/view/create_headline_page.dart

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,12 +214,15 @@ class _CreateHeadlineViewState extends State<_CreateHeadlineView> {
214214
.add(CreateHeadlineTopicChanged(value)),
215215
),
216216
const SizedBox(height: AppSpacing.lg),
217-
SearchableDropdownFormField<Country>(
217+
SearchableDropdownFormField<Country, CreateHeadlineBloc,
218+
CreateHeadlineState>(
218219
labelText: l10n.countryName,
219-
items: state.countries,
220+
bloc: context.read<CreateHeadlineBloc>(),
220221
initialValue: state.eventCountry,
221-
hasMore: state.countriesHasMore,
222-
isLoading: state.status == CreateHeadlineStatus.loading,
222+
itemsExtractor: (state) => state.countries,
223+
hasMoreExtractor: (state) => state.countriesHasMore,
224+
isLoadingExtractor: (state) =>
225+
state.status == CreateHeadlineStatus.loading,
223226
onChanged: (value) => context
224227
.read<CreateHeadlineBloc>()
225228
.add(CreateHeadlineCountryChanged(value)),

lib/content_management/view/create_source_page.dart

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,15 @@ class _CreateSourceViewState extends State<_CreateSourceView> {
161161
.add(CreateSourceUrlChanged(value)),
162162
),
163163
const SizedBox(height: AppSpacing.lg),
164-
SearchableDropdownFormField<Language>(
164+
SearchableDropdownFormField<Language, CreateSourceBloc,
165+
CreateSourceState>(
165166
labelText: l10n.language,
166-
items: state.languages,
167+
bloc: context.read<CreateSourceBloc>(),
167168
initialValue: state.language,
168-
hasMore: state.languagesHasMore,
169-
isLoading: state.status == CreateSourceStatus.loading,
169+
itemsExtractor: (state) => state.languages,
170+
hasMoreExtractor: (state) => state.languagesHasMore,
171+
isLoadingExtractor: (state) =>
172+
state.status == CreateSourceStatus.loading,
170173
onChanged: (value) => context
171174
.read<CreateSourceBloc>()
172175
.add(CreateSourceLanguageChanged(value)),
@@ -206,12 +209,15 @@ class _CreateSourceViewState extends State<_CreateSourceView> {
206209
.add(CreateSourceTypeChanged(value)),
207210
),
208211
const SizedBox(height: AppSpacing.lg),
209-
SearchableDropdownFormField<Country>(
212+
SearchableDropdownFormField<Country, CreateSourceBloc,
213+
CreateSourceState>(
210214
labelText: l10n.headquarters,
211-
items: state.countries,
215+
bloc: context.read<CreateSourceBloc>(),
212216
initialValue: state.headquarters,
213-
hasMore: state.countriesHasMore,
214-
isLoading: state.status == CreateSourceStatus.loading,
217+
itemsExtractor: (state) => state.countries,
218+
hasMoreExtractor: (state) => state.countriesHasMore,
219+
isLoadingExtractor: (state) =>
220+
state.status == CreateSourceStatus.loading,
215221
onChanged: (value) => context
216222
.read<CreateSourceBloc>()
217223
.add(CreateSourceHeadquartersChanged(value)),

lib/content_management/view/edit_headline_page.dart

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -282,12 +282,15 @@ class _EditHeadlineViewState extends State<_EditHeadlineView> {
282282
.add(EditHeadlineTopicChanged(value)),
283283
),
284284
const SizedBox(height: AppSpacing.lg),
285-
SearchableDropdownFormField<Country>(
285+
SearchableDropdownFormField<Country, EditHeadlineBloc,
286+
EditHeadlineState>(
286287
labelText: l10n.countryName,
287-
items: state.countries,
288+
bloc: context.read<EditHeadlineBloc>(),
288289
initialValue: selectedCountry,
289-
hasMore: state.countriesHasMore,
290-
isLoading: state.status == EditHeadlineStatus.loading,
290+
itemsExtractor: (state) => state.countries,
291+
hasMoreExtractor: (state) => state.countriesHasMore,
292+
isLoadingExtractor: (state) =>
293+
state.status == EditHeadlineStatus.loading,
291294
onChanged: (value) => context
292295
.read<EditHeadlineBloc>()
293296
.add(EditHeadlineCountryChanged(value)),

lib/content_management/view/edit_source_page.dart

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,15 @@ class _EditSourceViewState extends State<_EditSourceView> {
191191
),
192192
),
193193
const SizedBox(height: AppSpacing.lg),
194-
SearchableDropdownFormField<Language>(
194+
SearchableDropdownFormField<Language, EditSourceBloc,
195+
EditSourceState>(
195196
labelText: l10n.language,
196-
items: state.languages,
197+
bloc: context.read<EditSourceBloc>(),
197198
initialValue: state.language,
198-
hasMore: state.languagesHasMore,
199-
isLoading: state.status == EditSourceStatus.loading,
199+
itemsExtractor: (state) => state.languages,
200+
hasMoreExtractor: (state) => state.languagesHasMore,
201+
isLoadingExtractor: (state) =>
202+
state.status == EditSourceStatus.loading,
200203
onChanged: (value) => context
201204
.read<EditSourceBloc>()
202205
.add(EditSourceLanguageChanged(value)),
@@ -236,12 +239,15 @@ class _EditSourceViewState extends State<_EditSourceView> {
236239
),
237240
),
238241
const SizedBox(height: AppSpacing.lg),
239-
SearchableDropdownFormField<Country>(
242+
SearchableDropdownFormField<Country, EditSourceBloc,
243+
EditSourceState>(
240244
labelText: l10n.headquarters,
241-
items: state.countries,
245+
bloc: context.read<EditSourceBloc>(),
242246
initialValue: state.headquarters,
243-
hasMore: state.countriesHasMore,
244-
isLoading: state.status == EditSourceStatus.loading,
247+
itemsExtractor: (state) => state.countries,
248+
hasMoreExtractor: (state) => state.countriesHasMore,
249+
isLoadingExtractor: (state) =>
250+
state.status == EditSourceStatus.loading,
245251
onChanged: (value) => context
246252
.read<EditSourceBloc>()
247253
.add(EditSourceHeadquartersChanged(value)),

lib/shared/widgets/searchable_dropdown_form_field.dart

Lines changed: 52 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:flutter/material.dart';
2+
import 'package:flutter_bloc/flutter_bloc.dart';
23
import 'package:ui_kit/ui_kit.dart';
34

45
/// A generic type for the builder function that creates list items in the
@@ -21,17 +22,19 @@ typedef SearchableDropdownSelectedItemBuilder<T> = Widget Function(
2122
/// This widget is generic and can be used for any type [T]. It requires
2223
/// builders for constructing the list items and the selected item display,
2324
/// as well as callbacks to handle searching and pagination.
24-
class SearchableDropdownFormField<T> extends FormField<T> {
25+
class SearchableDropdownFormField<T, B extends BlocBase<S>, S>
26+
extends FormField<T> {
2527
/// {@macro searchable_dropdown_form_field}
2628
SearchableDropdownFormField({
27-
required List<T> items,
29+
required B bloc,
30+
required List<T> Function(S state) itemsExtractor,
31+
required bool Function(S state) hasMoreExtractor,
32+
required bool Function(S state) isLoadingExtractor,
2833
required ValueChanged<T?> onChanged,
2934
required ValueChanged<String> onSearchChanged,
3035
required VoidCallback onLoadMore,
3136
required SearchableDropdownItemBuilder<T> itemBuilder,
3237
required SearchableDropdownSelectedItemBuilder<T> selectedItemBuilder,
33-
required bool hasMore,
34-
bool? isLoading,
3538
super.key,
3639
T? initialValue,
3740
String? labelText,
@@ -49,13 +52,14 @@ class SearchableDropdownFormField<T> extends FormField<T> {
4952
onTap: () async {
5053
final selectedItem = await showDialog<T>(
5154
context: state.context,
52-
builder: (context) => _SearchableSelectionDialog<T>(
53-
items: items,
55+
builder: (context) => _SearchableSelectionDialog<T, B, S>(
56+
bloc: bloc,
57+
itemsExtractor: itemsExtractor,
58+
hasMoreExtractor: hasMoreExtractor,
59+
isLoadingExtractor: isLoadingExtractor,
5460
onSearchChanged: onSearchChanged,
5561
onLoadMore: onLoadMore,
5662
itemBuilder: itemBuilder,
57-
hasMore: hasMore,
58-
isLoading: isLoading ?? false,
5963
searchHintText: searchHintText,
6064
noItemsFoundText: noItemsFoundText,
6165
),
@@ -83,35 +87,38 @@ class SearchableDropdownFormField<T> extends FormField<T> {
8387
}
8488

8589
/// The modal dialog that contains the searchable and paginated list.
86-
class _SearchableSelectionDialog<T> extends StatefulWidget {
90+
class _SearchableSelectionDialog<T, B extends BlocBase<S>, S>
91+
extends StatefulWidget {
8792
const _SearchableSelectionDialog({
88-
required this.items,
93+
required this.bloc,
94+
required this.itemsExtractor,
95+
required this.hasMoreExtractor,
96+
required this.isLoadingExtractor,
8997
required this.onSearchChanged,
9098
required this.onLoadMore,
9199
required this.itemBuilder,
92-
required this.hasMore,
93-
required this.isLoading,
94100
this.searchHintText,
95101
this.noItemsFoundText,
96102
super.key,
97103
});
98104

99-
final List<T> items;
105+
final B bloc;
106+
final List<T> Function(S state) itemsExtractor;
107+
final bool Function(S state) hasMoreExtractor;
108+
final bool Function(S state) isLoadingExtractor;
100109
final ValueChanged<String> onSearchChanged;
101110
final VoidCallback onLoadMore;
102111
final SearchableDropdownItemBuilder<T> itemBuilder;
103-
final bool hasMore;
104-
final bool isLoading;
105112
final String? searchHintText;
106113
final String? noItemsFoundText;
107114

108115
@override
109-
State<_SearchableSelectionDialog<T>> createState() =>
110-
_SearchableSelectionDialogState<T>();
116+
State<_SearchableSelectionDialog<T, B, S>> createState() =>
117+
_SearchableSelectionDialogState<T, B, S>();
111118
}
112119

113-
class _SearchableSelectionDialogState<T>
114-
extends State<_SearchableSelectionDialog<T>> {
120+
class _SearchableSelectionDialogState<T, B extends BlocBase<S>, S>
121+
extends State<_SearchableSelectionDialog<T, B, S>> {
115122
final _scrollController = ScrollController();
116123
final _searchController = TextEditingController();
117124

@@ -168,7 +175,16 @@ class _SearchableSelectionDialogState<T>
168175
),
169176
const SizedBox(height: AppSpacing.md),
170177
Expanded(
171-
child: _buildList(),
178+
child: BlocBuilder<B, S>(
179+
bloc: widget.bloc,
180+
builder: (context, state) {
181+
final items = widget.itemsExtractor(state);
182+
final hasMore = widget.hasMoreExtractor(state);
183+
final isLoading = widget.isLoadingExtractor(state);
184+
185+
return _buildList(items, hasMore, isLoading);
186+
},
187+
),
172188
),
173189
],
174190
),
@@ -177,29 +193,34 @@ class _SearchableSelectionDialogState<T>
177193
);
178194
}
179195

180-
Widget _buildList() {
181-
if (widget.isLoading && widget.items.isEmpty) {
196+
Widget _buildList(List<T> items, bool hasMore, bool isLoading) {
197+
if (isLoading && items.isEmpty) {
182198
return const Center(child: CircularProgressIndicator());
183199
}
184200

185-
if (widget.items.isEmpty) {
201+
if (items.isEmpty) {
186202
return Center(
187203
child: Text(widget.noItemsFoundText ?? 'No items found.'),
188204
);
189205
}
190206

191207
return ListView.builder(
192208
controller: _scrollController,
193-
itemCount:
194-
widget.hasMore ? widget.items.length + 1 : widget.items.length,
209+
itemCount: items.length + (hasMore ? 1 : 0),
195210
itemBuilder: (context, index) {
196-
if (index >= widget.items.length) {
197-
return const Padding(
198-
padding: EdgeInsets.symmetric(vertical: AppSpacing.md),
199-
child: Center(child: CircularProgressIndicator()),
200-
);
211+
if (index >= items.length) {
212+
// This is the last item, which is the loading indicator.
213+
// It's only shown if we have more items and are currently loading.
214+
return isLoading
215+
? const Padding(
216+
padding: EdgeInsets.symmetric(vertical: AppSpacing.md),
217+
child: Center(child: CircularProgressIndicator()),
218+
)
219+
: const SizedBox.shrink();
201220
}
202-
final item = widget.items[index];
221+
222+
// This is a regular item.
223+
final item = items[index];
203224
return InkWell(
204225
onTap: () => Navigator.of(context).pop(item),
205226
child: widget.itemBuilder(context, item),

0 commit comments

Comments
 (0)