w461
w461

Reputation: 2678

flutter: BLoC Builder not updating, though I see new/different state in debugger

My 2nd strange event today. When I follow the debugger inside bloc.dart (the library, not my file) and I see that transition.nextState == state && _emitted is false, it does not update my widget (nextState has list size 2, state has 10 - _emitted is true). To my understanding the builder should be ok, is there any other spot to look at why it does not update?

Just to explain the logic before I show the code

  1. I fetch my data upon app launch with

    BlocProvider(create: (context) =>
      LogicGraphsPStmntBloc(logicGraphsRepository:
      logicGraphsRepository)..add(LogicGraphsNextProblemRequested()),
    
  2. this returns a list of 10 strings, which I store in my _bloc.dart file (not sure if this is state of the art)

  3. These 10 strings are sent via the state LogicGraphsPStmntLoadSuccess(statements) and are properly rendered as buttons

  4. If I click a button, only the 1st and the selected string shall be shown. The event is properly triggered with onPressed: () {BlocProvider.of<LogicGraphsPStmntBloc>(context).add(LogicGraphsPStmntCollapsed(statement: index-1));},

  5. This properly triggers the state yield LogicGraphsPStmntLoadSuccess(statementsShown); with now only 2 elements in the list (as compared to 10 in step 3) but it does not cause a rerendering

  6. If I step into the state with the debugger, I can see that both states have a different number of elements.

my states

abstract class LogicGraphsPStmntState extends Equatable {
  const LogicGraphsPStmntState();
  @override
  List<Object> get props => [];
}

class LogicGraphsPStmntLoadSuccess extends LogicGraphsPStmntState {
  const LogicGraphsPStmntLoadSuccess([this.statements = const []]);
  final List<String> statements;

  @override
  List<Object> get props => [];

  @override
  String toString() => 'LogicGraphsPStmntLoadSuccess { statements: $statements }';
}

my bloc (not exactly, because the code inside my sub functions to release a state are not reached as described in my other issue. So currently I have those sub functions lines as spaghetti in the mapEventToState=

class LogicGraphsPStmntBloc extends Bloc<LogicGraphsPStmntEvent, LogicGraphsPStmntState> {
  LogicGraphsPStmntBloc({@required this.logicGraphsRepository}) : super(LogicGraphsPStmntInProgress());
  final LogicGraphsRepository logicGraphsRepository;
  List<String> statements;
  int currentProblem = -1;

  @override
  Stream<LogicGraphsPStmntState> mapEventToState(LogicGraphsPStmntEvent event) async* {
        if (event is LogicGraphsNextProblemRequested) {
          _mapLGPStmntNewProblemRequested();
          _mapLGPStmntLoadRequested(currentProblem);
        }
        else if (event is LogicGraphsPStmntLoadRequested) {
          _mapLGPStmntLoadRequested(currentProblem);
        }
        else if (event is LogicGraphsPStmntCollapsed) {
          _mapLGPStmntCollapseRequested(event);
  }
  Stream<LogicGraphsPStmntState> _mapLGPStmntNewProblemRequested() async* {
        try {
          currentProblem =
          await getNextProblem(currentProblem == null ? -1 : currentProblem);
          statements =
          await logicGraphsRepository.getProblemStmntList(currentProblem);
          _mapLGPStmntLoadRequested(currentProblem);
        } catch (_) {
          yield LogicGraphsPStmntLoadFailure();
        }
  }
  Stream<LogicGraphsPStmntState> _mapLGPStmntLoadRequested(int problem) async* {
          try {
            yield LogicGraphsPStmntLoadSuccess(statements);
          } catch (_) {
            yield LogicGraphsPStmntLoadFailure();
          }
  }
  Stream<LogicGraphsPStmntState> _mapLGPStmntCollapseRequested(
          LogicGraphsPStmntCollapsed event) async* {
          List<String> statementsShown = [];
          statementFocussed = event.statement;
          statementsShown.add(statements[0]);
          statementsShown.add(statements[statementFocussed]);
          yield LogicGraphsPStmntLoadSuccess(statementsShown);
  }
}

my widgets

class LogicGraphPage extends StatelessWidget {
  final LogicGraphsRepository logicGraphsRepository = LogicGraphsRepository(
      logicGraphsApiClient: LogicGraphsApiClient());

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      child: BlocProvider(
        create: (context) =>
        LogicGraphsPStmntBloc(logicGraphsRepository: logicGraphsRepository)
          ..add(LogicGraphsNextProblemRequested()),
          child:BlocBuilder<LogicGraphsPStmntBloc, LogicGraphsPStmntState>(
            builder: (context, state) {
              if (state is LogicGraphsPStmntLoadSuccess) {
                final List<String> statements = state.statements;
                return ListView.builder(
                    itemCount: statements.length,
                    itemBuilder: (BuildContext context, int index) {
                      return CupertinoButton(
                        padding: EdgeInsets.all(0),
                        child: Text(statements[index])
                        onPressed: () {BlocProvider.of<LogicGraphsPStmntBloc>(context).add(LogicGraphsPStmntCollapsed(statement: index-1));},
                      );
                    });
              }
              else return Container();
            }
        )
      ),
    );
  }
}

The function inside the bloc.dart library where I would assume that the new state is emitted. You see the line if (transition.nextState == state && _emitted) return;. Next state has 2 elements, state has 10, _emitted is true, so I would assume the code to continue. Instead the return is triggered and lines withemit(transition.nextState); are skipped.

Any help is highly appreciated, since this is my first experience with BLoC/equatable and flutter in general is also still quite new to me. But I am progressing!

 void _bindEventsToStates() {
    _transitionSubscription = transformTransitions(
      transformEvents(
        _eventController.stream,
            (event) => mapEventToState(event).map(
              (nextState) => Transition(
            currentState: state,
            event: event,
            nextState: nextState,
          ),
        ),
      ),
    ).listen(
          (transition) {
        if (transition.nextState == state && _emitted) return; // <<<<<<<<<<<
        try {
          onTransition(transition);
          emit(transition.nextState);
        } on dynamic catch (error, stackTrace) {
          onError(error, stackTrace);
        }
        _emitted = true;
      },
      onError: onError,
    );
  }

Upvotes: 1

Views: 2058

Answers (1)

Nicol&#242; Bozzato
Nicol&#242; Bozzato

Reputation: 86

I guess the bloc can't tell the difference between LogicGraphsPStmntLoadSuccess(statements) and LogicGraphsPStmntLoadSuccess(statementsShown) as they are the same state and I think it cannot spot the differences between the different parameters.

So what I suggest to do is this: before yielding LogicGraphsPStmntLoadSuccess(statementsShown) try to yield a "switching" state like I don't know LogicGraphsPStmntChangeStatement before that. So:

yield LogicGraphsPStmntChangeStatement;
yield LogicGraphsPStmntLoadSuccess(statementsShown);

That should do the trick.

Upvotes: 2

Related Questions