flutter bloc: change color on each item when tap

Issue

I am trying to use flutter bloc so i create this bloc:

class CategoriesBloc extends Bloc<CategoriesEvent, CategoriesState> {
  CategoriesBloc() : super(CategoriesInitial());
  List<CategoriesItem> _catItems = [
    CategoriesItem(id: 1, title: "Sugar", prefixIcon: Icons.access_alarms),
    CategoriesItem(id: 2, title: "Calories", prefixIcon: Icons.access_alarms),
    CategoriesItem(id: 3, title: "Salt", prefixIcon: Icons.access_alarms),
    CategoriesItem(id: 4, title: "Fibre", prefixIcon: Icons.access_alarms),
    CategoriesItem(id: 5, title: "Fat", prefixIcon: Icons.access_alarms)
  ];

  List<CategoriesItem> get items => _catItems;

  @override
  Stream<CategoriesState> mapEventToState(
    CategoriesEvent event,
  ) async* {
    if (event is TopCategoriesEvent) {
      yield* _makeCatList(items);
    }
  }
}

Stream<CategoriesState> _makeCatList(List<CategoriesItem> items) async* {
  yield CategoriesStarted(items);
}

class CategoriesItem {
  final int id;
  final String title;
  final IconData prefixIcon;
  final IconData suffixIcon;
  bool selected;

  CategoriesItem(
      {this.id,
      this.title,
      this.prefixIcon,
      this.suffixIcon,
      this.selected = false});
}

Now in main page i am using like this:

Container(
              height: 80,
              color: Colors.green[500],
              child: BlocBuilder<CategoriesBloc, CategoriesState>(
                  builder: (context, state) {
                if (state is CategoriesInitial) {
                  return Center(
                    child: CircularProgressIndicator(),
                  );
                } else if (state is CategoriesStarted) {
                  return _makeCatItems(state.catItems);
                }
                return Container();
              }),

and this is my _makeCatItems method:

 Widget _makeCatItems(List<CategoriesItem> catItems) {
    print(catItems.length);
    return Container(
      margin: EdgeInsets.only(top: 8.0),
      child: ListView(
        scrollDirection: Axis.horizontal,
        shrinkWrap: true,
        children: catItems
            .map(
              (item) => Row(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  InkWell(
                    onTap: () {},
                    child: Container(
                        width: 100,
                        height: 40,
                        alignment: Alignment.center,
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(8.0),
                          color: Colors.green[300],// change to white when tapped
                        ),
                        child: Row(
                          mainAxisAlignment: MainAxisAlignment.spaceAround,
                          children: <Widget>[
                            Icon(
                              item.prefixIcon,
                              color: Colors.white,
                            ),
                            Text(
                              item.title,
                              style: TextStyle(color: Colors.white),
                            )
                          ],
                        )),
                  ),
                  SizedBox(
                    width: 16.0,
                  )
                ],
              ),
            )
            .toList(),
      ),
    );
  }

How could i change background item color when user clicked on each item in bloc way?

Solution

You could add a List of tapped items, initially empty list, in the state, add an event which passes an item in the field and change the color in BoxDecoration to state.tappedItems.contains(item) ? Colors.white : Colors.green[300]. You need to change onTap too, something like this:

state.tappedItems.contains(item) 
  ? context.bloc<CategoriesBloc>().add(AddItem())  
  : context.bloc<CategoriesBloc>().add(RemoveItem()),

makeCatItems will also need to get state as an argument or be another BlocBuilder, whichever feels better for you

E:

Change state into (you need to change CategoriesState too, add final List<CategoriesItem> pressedItems to the fields)

Stream<CategoriesState> _makeCatList(List<CategoriesItem> items, List<CategoriesItem> pressedItems) async* {
  yield CategoriesStarted(items, pressedItems);
}

Then you need to add events (CategoryRemoved will be almost the same, just copy and change the name)

class CategoryAdded extends CategoryEvent {
  final CategoryItem item;

  CategoryAdded(CategoryItem item);
}

So you have both state and event, now bloc needs to handle the changes.

  @override
  Stream<CategoriesState> mapEventToState(
    CategoriesEvent event,
  ) async* {
    if (event is TopCategoriesEvent) {
      yield* _makeCatList(items);
    } else if (event is CategoryAdded) {
      yield CategoryState(state.items, state.pressedItems..add(event.item));
    } else if (event is CategoryRemoved {
      yield CategoryState(state.items, state.pressedItems..remove(event.item));
    }
  }

Now your logic is set, now you just need to change the widget

 Widget _makeCatItems() {
    return BlocBuilder<CategoryBloc, CategoryState>(
      builder: (context, state) => Container(
      margin: EdgeInsets.only(top: 8.0),
      child: ListView(
        scrollDirection: Axis.horizontal,
        shrinkWrap: true,
        children: catItems
            .map(
              (item) => Row(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  InkWell(
                    onTap: () {context.bloc<CategoryBloc>().add(state.pressedItems.contains(item) ? RemoveCategory(item) : AddCategory(item)},
                    child: Container(
                        width: 100,
                        height: 40,
                        alignment: Alignment.center,
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(8.0),
                          color: state.pressedItems.contains(item) ? Colors.white : Colors.green[300],
                        ),
                        child: Row(
                          mainAxisAlignment: MainAxisAlignment.spaceAround,
                          children: <Widget>[
                            Icon(
                              item.prefixIcon,
                              color: Colors.white,
                            ),
                            Text(
                              item.title,
                              style: TextStyle(color: Colors.white),
                            )
                          ],
                        )),
                  ),
                  SizedBox(
                    width: 16.0,
                  )
                ],
              ),
            )
            .toList(),
      ),
    );
  );
  }

Answered By – Kamil Poniewierski

Answer Checked By – Marie Seifert (FlutterFixes Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *