GIOVANI ARDIANSYAH
GIOVANI ARDIANSYAH

Reputation: 23

Flutter BLoC can't update my list of boolean

So, I tried to learn flutter especially in BLoC method and I made a simple ToggleButtons with BLoC. Here it looks like

ToggleUI.dart

class Flutter501 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter 50 With Bloc Package',
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              BlocProvider<ToggleBloc>(
                builder: (context) => ToggleBloc(maxToggles: 4),
                child: MyToggle(),
              )
            ],
          ),
        ),
      ),
    );
  }
}

class MyToggle extends StatelessWidget {
  const MyToggle({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    ToggleBloc bloc = BlocProvider.of<ToggleBloc>(context);
    return BlocBuilder<ToggleBloc, List<bool>>(
      bloc: bloc,
      builder: (context, state) {
        return ToggleButtons(
          children: [
            Icon(Icons.arrow_back),
            Icon(Icons.arrow_upward),
            Icon(Icons.arrow_forward),
            Icon(Icons.arrow_downward),
          ],
          onPressed: (idx) {
            bloc.dispatch(ToggleTap(index: idx));
          },
          isSelected: state,
        );
      },
    );
  }
}

ToogleBloc.dart

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';

abstract class ToggleEvent extends Equatable {
  const ToggleEvent();
}

class ToggleTap extends ToggleEvent {
  final int index;

  ToggleTap({this.index});

  @override
  // TODO: implement props
  List<Object> get props => [];
}

class ToggleBloc extends Bloc<ToggleEvent, List<bool>> {
  final List<bool> toggles = [];

  ToggleBloc({
    @required int maxToggles,
  }) {
    for (int i = 0; i < maxToggles; i++) {
      this.toggles.add(false);
    }
  }

  @override
  // TODO: implement initialState
  List<bool> get initialState => this.toggles;

  @override
  Stream<List<bool>> mapEventToState(ToggleEvent event) async* {
    // TODO: implement mapEventToState
    if (event is ToggleTap) {
      this.toggles[event.index] = !this.toggles[event.index];
    }

    yield this.toggles;
  }
}

The problem came when I tried to Tap/Press one of the buttons, but it doesn't want to change into the active button. But it works whenever I tried to press the "Hot Reload". It likes I have to make a setState whenever the button pressed.

Upvotes: 2

Views: 3878

Answers (2)

dshukertjr
dshukertjr

Reputation: 18690

BlocBuilder will ignore the update if a new state was equal to the old state. When comparing two lists in Dart language, if they are the same instance, they are equal, otherwise, they are not equal.

So, in your case, you would have to create a new instance of list for every state change, or define a state object and send your list as property of it.

Here is how you would create new list instance for every state:

if (event is ToggleTap) {
  this.toggles[event.index] = !this.toggles[event.index];
}

yield List.from(this.toggles);

You can read more about bloc library and equality here: https://bloclibrary.dev/#/faqs?id=when-to-use-equatable

Upvotes: 2

Tim Klingeleers
Tim Klingeleers

Reputation: 3034

The BlocBuilder.builder method is only executed if the State changes. So in your case the State is a List<bool> of which you only change a specific index and yield the same object. Because of this, BlocBuilder can't determine if the List changed and therefore doesn't trigger a rebuild of the UI.

See https://github.com/felangel/bloc/blob/master/docs/faqs.md for the explanation in the flutter_bloc docs:

Equatable properties should always be copied rather than modified. If an Equatable class contains a List or Map as properties, be sure to use List.from or Map.from respectively to ensure that equality is evaluated based on the values of the properties rather than the reference.

Solution

In your ToggleBloc, change the List like this, so it creates a completely new List object:

  @override
  Stream<List<bool>> mapEventToState(ToggleEvent event) async* {
    // TODO: implement mapEventToState
    if (event is ToggleTap) {
      this.toggles[event.index] = !this.toggles[event.index];
      this.toggles = List.from(this.toggles);
    }

    yield this.toggles;
  }

Also, make sure to set the props for your event, although it won't really matter for this specific question.

Upvotes: 2

Related Questions