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