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

23 views Asked by At

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

1

There are 1 answers

0
Rahul On BEST ANSWER

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);
  }
}