乐于分享
好东西不私藏

Build a Todo App with Flutter(20)

Build a Todo App with Flutter(20)

The stats feature displays statistics about the active and completed todos.

StatsState

StatsState keeps track of summary information and the current StatsStatus.

part of 'stats_bloc.dart';enum StatsStatus { initial, loading, success, failure }final classStatsStateextendsEquatable{  const StatsState({this.status = StatsStatus.initial,this.completedTodos = 0,this.activeTodos = 0,  });  final StatsStatus status;  final int completedTodos;  final int activeTodos;  @override  List<Objectget props => [status, completedTodos, activeTodos];  StatsState copyWith({    StatsStatus? status,int? completedTodos,int? activeTodos,  }) {return StatsState(      status: status ?? this.status,      completedTodos: completedTodos ?? this.completedTodos,      activeTodos: activeTodos ?? this.activeTodos,    );  }}

StatsEvent

StatsEvent has only one event called StatsSubscriptionRequested:

part of 'stats_bloc.dart';sealed classStatsEventextendsEquatable{  const StatsEvent();  @override  List<Objectget props => [];}final classStatsSubscriptionRequestedextendsStatsEvent{  const StatsSubscriptionRequested();}

StatsBloc

StatsBloc depends on the TodosRepository just like TodosOverviewBloc. It subscribes to the todos stream via _todosRepository.getTodos.

part'stats_event.dart';part 'stats_state.dart';classStatsBlocextendsBloc<StatsEventStatsState{  StatsBloc({required TodosRepository todosRepository,  }) : _todosRepository = todosRepository,super(const StatsState()) {on<StatsSubscriptionRequested>(_onSubscriptionRequested);  }  final TodosRepository _todosRepository;  Future<void> _onSubscriptionRequested(    StatsSubscriptionRequested event,    Emitter<StatsState> emit,  ) async {    emit(state.copyWith(status: StatsStatus.loading));await emit.forEach<List<Todo>>(      _todosRepository.getTodos(),      onData: (todos) => state.copyWith(        status: StatsStatus.success,        completedTodos: todos.where((todo) => todo.isCompleted).length,        activeTodos: todos.where((todo) => !todo.isCompleted).length,      ),      onError: (_, _) => state.copyWith(status: StatsStatus.failure),    );  }}

StatsView

view.dart is the barrel file for the stats_page.

export'stats_page.dart';

stats_page.dart contains the UI for the page that displays the todos statistics.

classStatsPageextendsStatelessWidget{  const StatsPage({super.key});  @override  Widget build(BuildContext context) {return BlocProvider(      create: (context) => StatsBloc(        todosRepository: context.read<TodosRepository>(),      )..add(const StatsSubscriptionRequested()),      child: const StatsView(),    );  }}classStatsViewextendsStatelessWidget{  const StatsView({super.key});  @override  Widget build(BuildContext context) {final l10n = context.l10n;final state = context.watch<StatsBloc>().state;final textTheme = Theme.of(context).textTheme;return Scaffold(      appBar: AppBar(        title: Text(l10n.statsAppBarTitle),      ),      body: Column(        children: [          ListTile(            key: const Key('statsView_completedTodos_listTile'),            leading: const Icon(Icons.check_rounded),            title: Text(l10n.statsCompletedTodoCountLabel),            trailing: Text('${state.completedTodos}',              style: textTheme.headlineSmall,            ),          ),          ListTile(            key: const Key('statsView_activeTodos_listTile'),            leading: const Icon(Icons.radio_button_unchecked_rounded),            title: Text(l10n.statsActiveTodoCountLabel),            trailing: Text('${state.activeTodos}',              style: textTheme.headlineSmall,            ),          ),        ],      ),    );  }}

A simplified representation of the widget tree for the StatsPage is:

├── StatsPage│   └── BlocProvider<StatsBloc>│       └── StatsView│           ├── context.watch<StatsBloc>│           └── Column

The TodosOverviewBloc and StatsBloc both communicate with the TodosRepository, but it is important to note there is no direct communication between the blocs.