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