Line data Source code
1 : import 'dart:collection'; 2 : 3 : import 'package:bloc/bloc.dart'; 4 : import 'package:flutter_bloc_patterns/src/list/base/list_events.dart'; 5 : import 'package:flutter_bloc_patterns/src/list/filter/filter_list_repository.dart'; 6 : import 'package:flutter_bloc_patterns/src/view/view_state.dart'; 7 : 8 : /// A list BLoC with allowing filtering capabilities but without pagination. 9 : /// Thus it should be used with a reasonable small amount of data. 10 : /// 11 : /// Designed to collaborate with [ViewStateBuilder] for displaying data. 12 : /// 13 : /// Call [loadElements] to perform initial data fetch. 14 : /// Call [refreshElements] to perform a refresh. 15 : /// 16 : /// [T] - the type of list elements. 17 : /// [F] - the type of filter. 18 : class FilterListBloc<T, F> extends Bloc<ListEvent, ViewState> { 19 : final FilterListRepository<T, F> _repository; 20 : F _filter; 21 : 22 2 : FilterListBloc(FilterListRepository<T, F> filterListRepository) 23 0 : : assert(filterListRepository != null), 24 : _repository = filterListRepository; 25 : 26 2 : @override 27 : ViewState get initialState => const Initial(); 28 : 29 0 : F get filter => _filter; 30 : 31 : /// Loads elements using the given [filter]. 32 : /// 33 : /// It's most suitable for initial data fetch or for retry action when 34 : /// the first fetch fails. It can also be used when [filter] changes when a 35 : /// full reload is required. 36 6 : void loadElements({F filter}) => add(LoadList(filter)); 37 : 38 : /// Refreshes elements using the given [filter]. 39 : /// 40 : /// The refresh is designed for being called after the initial fetch 41 : /// succeeds. It can be performed when the list has already been loaded. 42 : /// 43 : /// It can be used when [filter] changes and there's no need for displaying a 44 : /// loading indicator. 45 6 : void refreshElements({F filter}) => add(RefreshList(filter)); 46 : 47 : @override 48 2 : Stream<ViewState> mapEventToState(ListEvent event) async* { 49 2 : if (event is LoadList<F>) { 50 6 : yield* _mapLoadList(event.filter); 51 4 : } else if (event is RefreshList<F> && _isRefreshPossible(event)) { 52 6 : yield* _mapRefreshList(event.filter); 53 : } 54 : } 55 : 56 2 : bool _isRefreshPossible(ListEvent event) => 57 6 : state is Success || state is Empty; 58 : 59 2 : Stream<ViewState> _mapLoadList(F filter) async* { 60 2 : yield const Loading(); 61 4 : yield* _getListState(filter); 62 : } 63 : 64 2 : Stream<ViewState> _mapRefreshList(F filter) async* { 65 2 : final elements = _getCurrentStateElements(); 66 4 : yield Refreshing(elements); 67 4 : yield* _getListState(filter); 68 : } 69 : 70 2 : List<T> _getCurrentStateElements() => 71 9 : (state is Success<List<T>>) ? (state as Success<List<T>>).data : []; 72 : 73 2 : Stream<ViewState> _getListState(F filter) async* { 74 : try { 75 4 : final List<T> elements = await _getElementsFromRepository(filter); 76 4 : yield elements.isNotEmpty 77 4 : ? Success<List<T>>(UnmodifiableListView(elements)) 78 : : const Empty(); 79 : } catch (e) { 80 4 : yield Failure(e); 81 : rethrow; 82 : } finally { 83 2 : _filter = filter; 84 : } 85 : } 86 : 87 2 : Future<List<T>> _getElementsFromRepository(F filter) { 88 : if (filter != null) { 89 2 : return _repository.getBy(filter); 90 : } else { 91 4 : return _repository.getAll(); 92 : } 93 : } 94 : }