Search code examples
flutterstate-managementflutter-cubit

Inconsistent State Updates in Flutter Counter App Using Cubit for Multiple Buttons


I am developing a simple counter app using Flutter, with Cubit for state management. My app features 6 buttons, each designed to add 1, 2, or 3 points to two players (Player A and Player B). However, I've encountered a strange issue: when I attempt to add 1 point to Player A's score using its dedicated button, the app sometimes adds an incorrect amount (2, 4, or even 10 points) instead of the expected 1 point. Interestingly, I'm using the same component for Player B, and it works perfectly without any issues.A counter App view code: https://github.com/As-tra/Counter

I try to modify the logic but it seems correct


Solution

  • I have made few changes and added relevant comments as well to help you understand.

    Remember, Bloc/cubit means events will be added to Bloc or method of cubit will be called and new state will correct state values will be emitted.

    import 'package:flutter/material.dart';
    import 'package:flutter_bloc/flutter_bloc.dart';
    import 'package:meta/meta.dart';
    
    void main() {
      WidgetsFlutterBinding.ensureInitialized();
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return BlocProvider(
          create: (context) => CounterCubit(),
          child: const MaterialApp(
            debugShowCheckedModeBanner: false,
            home: HomePage(),
          ),
        );
      }
    }
    
    class HomePage extends StatelessWidget {
      const HomePage({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            backgroundColor: Colors.orange,
            title: const Text(
              'Points Counter',
              style: TextStyle(color: Colors.white),
            ),
          ),
          body: BlocBuilder<CounterCubit, CounterIncrementState>(
            builder: (context, state) {
              return Column(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    children: [
                      SizedBox(
                        height: 500,
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                          children: [
                            const Text(
                              'Team A',
                              style: TextStyle(fontSize: 32),
                            ),
                            Text(
                              // use state variable to get current score value
                              '${state.teamA}',
                              style: const TextStyle(fontSize: 150),
                            ),
                            const CustomButton(points: 1, team: 'A'),
                            const CustomButton(points: 2, team: 'A'),
                            const CustomButton(points: 3, team: 'A'),
                          ],
                        ),
                      ),
                      const SizedBox(
                        height: 500,
                        child: VerticalDivider(
                          indent: 50,
                          endIndent: 50,
                        ),
                      ),
                      SizedBox(
                        height: 500,
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                          children: [
                            const Text(
                              'Team B',
                              style: TextStyle(fontSize: 32),
                            ),
                            Text(
                              '${state.teamB}',
                              style: const TextStyle(fontSize: 150),
                            ),
                            const CustomButton(points: 1, team: 'B'),
                            const CustomButton(points: 2, team: 'B'),
                            const CustomButton(points: 3, team: 'B'),
                          ],
                        ),
                      ),
                    ],
                  ),
                  ElevatedButton(
                    onPressed: () {
                      context.read<CounterCubit>().reset();
                    },
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Colors.orange,
                      foregroundColor: Colors.white,
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(8),
                      ),
                      minimumSize: const Size(150, 50),
                    ),
                    child: const Text(
                      'Reset',
                      style: TextStyle(
                        color: Colors.black,
                        fontSize: 18,
                      ),
                    ),
                  ),
                ],
              );
            },
          ),
        );
      }
    }
    
    class CustomButton extends StatelessWidget {
      final int points;
      final String team;
      const CustomButton({
        super.key,
        required this.points,
        required this.team,
      });
    
      @override
      Widget build(BuildContext context) {
        return ElevatedButton(
          onPressed: () {
            context.read<CounterCubit>().teamIncrement(team: team, points: points);
          },
          style: ElevatedButton.styleFrom(
            backgroundColor: Colors.orange,
            foregroundColor: Colors.white,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(8),
            ),
            minimumSize: const Size(150, 50),
          ),
          child: Text(
            'Add $points point',
            style: const TextStyle(
              color: Colors.black,
              fontSize: 18,
            ),
          ),
        );
      }
    }
    
    class CounterCubit extends Cubit<CounterIncrementState> {
      CounterCubit() : super(CounterIncrementState.uninitialized());
    
      // teamA and teamB moved to state class.
    
      void teamIncrement({required String team, required int points}) {
        if (team == 'A') {
          emit(state.copyWith(teamA: state.teamA + points));
        } else {
          emit(state.copyWith(teamB: state.teamB + points));
        }
      }
    
      void reset() {
        emit(const CounterIncrementState(teamA: 0, teamB: 0));
      }
    }
    
    // Have the team score as part of state
    // on each operation, you can emit new state object with current score.
    @immutable
    class CounterIncrementState {
      final int teamA;
      final int teamB;
    
      const CounterIncrementState({required this.teamA, required this.teamB});
    
      // copy constructor to get new state object based on current object and
      // passed params.
      CounterIncrementState copyWith({
        int? teamA,
        int? teamB,
      }) {
        return CounterIncrementState(
          teamA: teamA ?? this.teamA,
          teamB: teamB ?? this.teamB,
        );
      }
    
      factory CounterIncrementState.uninitialized() {
        return const CounterIncrementState(teamA: 0, teamB: 0);
      }
    }