Bloc: Bloc builder in List rebuilding only once

Created on 28 May 2020  路  4Comments  路  Source: felangel/bloc

Hi there, I'm trying to implement Bloc in a large application and I'm having some issues right now, I have successfully added a Bloc using BlocProvider, BlocListener and BlocBuilder elements, so, the thing is that I see no state change after yielding on bloc event handling, apparently the only way I have found so far to trigger a UI Build is to use setState manually. I also notice that the listener inside my widget is not receiving any state change no matter if I yield the state from my bloc, I'm having issues with RegistrationBloc and LikesSelected state, I also have this widget as a child of a BlocProvider declaration.
The hierarchy of elements is
RegistrationView which has a Provider in top of Scaffold
Inside I have several BlocBuilders and BlocListeners which are working properly.
And as child of my main RegistrationView i have a function that returns Widgets based on a List of widgets

This is part of the bloc, i excluded not relevant functions and variables:
````
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:spmobile/src/models/Channel.dart';
import 'registration_state.dart';
import 'registration_event.dart';
import 'package:spmobile/src/models/Likes.dart';
import 'package:spmobile/src/app_services/registrationService.dart';

class RegistrationBloc extends Bloc {
int _activeView = 0;
List _selectedLikes = [];
final _registrationService = RegistrationService();

List get selectedLikes => _selectedLikes;

void addLikes(Likes like){
_selectedLikes.add(like);
}

void removeLikes(Likes key) {
_selectedLikes.removeWhere((item) => item.key == key.key);
}

@override
RegistrationState get initialState => RegistrationFormDisplayed();

@override
Stream mapEventToState(RegistrationEvent event) async* {
if (event is SelectLikes) {
addLikes(event.like);
yield LikesSelected(_selectedLikes);
}
if (event is DeSelectLikes) {
removeLikes(event.like);
yield LikesSelected(_selectedLikes);
}
}

void next() {
_activeView++;
}

}
And my implementation is basically this:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:spmobile/src/bloc/registration/registration.dart';
import 'package:spmobile/src/models/Likes.dart';

class LikesView extends StatefulWidget {
@override
_LikesViewState createState() => _LikesViewState();
}

class _LikesViewState extends State {

final List _keys = [
'restaurants', 'travel', 'beauty', 'health', 'movies', 'music', 'culture', 'home', 'vehicles', 'events', 'pets', 'fitness'
];

final List items = ['Restaurantes', 'Viajes', 'Belleza', 'Salud', 'Peliculas', 'M煤sica', 'Cultura', 'Hogar', 'Vehiculos', 'Eventos', 'Mascotas', 'Fitness'];

@override
Widget build(BuildContext context) {

RegistrationBloc _registrationBloc = BlocProvider.of<RegistrationBloc>(context);
Widget likesList() {
  return BlocListener(
    bloc: _registrationBloc,
    listener: (context, state){
      if (state is LikesSelected) {
        print(state.likesList);
      }
    },
    child: BlocBuilder(
      bloc: _registrationBloc,
      builder: (context, state) {
        print("REBUILDE");
        return ListView.separated(
          itemCount: items.length,
          itemBuilder: (context, int index) {
            return SwitchListTile(
                activeColor: Color(0xFFC62D29),
                contentPadding: EdgeInsets.only(left:25.0, right: 20.0),
                title: Text(items[index], style: TextStyle(fontWeight: FontWeight.bold),),
                value: _registrationBloc.selectedLikes.indexWhere((item) => item.key == _keys[index]) > -1,
                onChanged: (bool value) {
                  setState(() {
                    Likes like = Likes(items[index],_keys[index]);
                    if(_registrationBloc.selectedLikes.length == 0) {
                      _registrationBloc.add(SelectLikes(like));
                    } else {
                      if (_registrationBloc.selectedLikes.indexWhere((element) => element.key == like.key)>-1) {
                        _registrationBloc.add(DeSelectLikes(like));
                      } else {
                        _registrationBloc.add(SelectLikes(like));
                      }
                    }
                  });
                });
          },
          separatorBuilder: (BuildContext context, int index) => const Divider());
      }
    ),
  );
}

return Column(
  children: <Widget>[
    Padding(
      padding: EdgeInsets.only(top:10.0, bottom:10.0),
      child: Text('Queremos saber tus gustos', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),),
    ),
    Padding(
      padding: const EdgeInsets.only(left: 30.0, right: 30.0),
      child: Container(
        height: 320,
        decoration: BoxDecoration(
          boxShadow: [
            BoxShadow(
              color: Colors.grey,
              offset: Offset(2,2)
            ),
          ],
            color: Colors.white, borderRadius: BorderRadius.circular(10)),
        child: likesList(),
      ),
    ),
  ],
);

}

@override
void dispose() {
super.dispose();
}
}
So that is the widget I'm connecting the bloc with, so, I'm pretty sure I have some errors going on like disposing and bad declarations but not sure if those are actually blocking my UI rebuild each time I send an event to the bloc. If someone needs access to this, I can grant it with the email, is just that the application is private. thanks in advance. This is how I handle events:
import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:spmobile/src/models/Likes.dart';

abstract class RegistrationEvent extends Equatable {
const RegistrationEvent();

@override
List get props => [];
}

class RegistrationFormEvent extends RegistrationEvent {}

class EmailChanged extends RegistrationEvent {
final String email;
const EmailChanged({@required this.email});

@override
List get props => [email];
}

class PhoneChanged extends RegistrationEvent {
final String phone;
const PhoneChanged({@required this.phone});

@override
List get props => [phone];
}

class SmsSelected extends RegistrationEvent {
final bool sms;
SmsSelected(this.sms);

@override
List get props => [sms];
}

class EmailSelected extends RegistrationEvent {
final bool emailOpt;
EmailSelected(this.emailOpt);

@override
List get props => [emailOpt];
}

class WhatsAppSelected extends RegistrationEvent {
final bool whatsapp;
WhatsAppSelected(this.whatsapp);

@override
List get props => [whatsapp];
}

class FormSubmitted extends RegistrationEvent {
final bool isFormValid;
FormSubmitted(this.isFormValid) : super();
}

class SelectLikes extends RegistrationEvent {
final Likes like;
SelectLikes(this.like);
}

class DeSelectLikes extends RegistrationEvent {
final Likes like;
DeSelectLikes(this.like);
}

class FormReset extends RegistrationEvent {}
and states:
import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';
import 'package:spmobile/src/models/Likes.dart';

class RegistrationState extends Equatable {
final String email;
final bool isEmailValid;
final String phone;
final bool isPhoneValid;
final bool formSubmittedSuccessfully;

bool get isFormValid {
if (isPhoneValid != null && isEmailValid != null) {
return isPhoneValid && isEmailValid;
} else {
return false;
}
}

RegistrationState({@required this.email, @required this.isEmailValid, @required this.phone,
@required this.isPhoneValid, @required this.formSubmittedSuccessfully});

@override
List get props {
return [
email,
isEmailValid,
phone,
isPhoneValid,
formSubmittedSuccessfully
];
}

RegistrationState copyWith({ email, isEmailValid, phone, isPhoneValid, formSubmittedSuccessfully}) {
return RegistrationState(
email: email ?? this.email,
isEmailValid: isEmailValid ?? this.isEmailValid,
phone: phone ?? this.phone,
isPhoneValid: isPhoneValid ?? this.isPhoneValid,
formSubmittedSuccessfully: formSubmittedSuccessfully ?? this.formSubmittedSuccessfully,
);
}
}

class RegistrationFormDisplayed extends RegistrationState {}

class EmailChannelSelected extends RegistrationState {
final bool emailChannel;
EmailChannelSelected(this.emailChannel);
}

class SmsChannelSelected extends RegistrationState {
final bool sms;
SmsChannelSelected(this.sms);
}

class WhatsAppChannelSelected extends RegistrationState {
final bool whatsapp;
WhatsAppChannelSelected(this.whatsapp);
}

class RegistrationFormValid extends RegistrationState {
final bool valid;
RegistrationFormValid(this.valid);
}

class SendingRegistrationForm extends RegistrationState {
SendingRegistrationForm();
}

class RegistrationCompleted extends RegistrationState {
final String record;
RegistrationCompleted({
String phone,
String email,
bool isEmailValid,
bool isPhoneValid,
bool formSubmittedSuccessfully,
@required this.record
}) :
super(
phone: phone,
email : email,
formSubmittedSuccessfully: formSubmittedSuccessfully,
isEmailValid: isEmailValid,
isPhoneValid: isPhoneValid
);
}

class RegistrationIncomplete extends RegistrationState {
final String error;
RegistrationIncomplete(this.error);

@override
List get props => [error];

@override
String toString() => 'Registration Failure { error: $error }';
}

class LikesSelected extends RegistrationState {
final List likesList;
LikesSelected(this.likesList);
}

class RegistrationFormInvalid extends RegistrationState {}
````

question

All 4 comments

Hi @hackerunet , it would be easier for you to get help if you could share a repo. Thanks 馃憤

I've taken a quick look at your code and you seem to have a lot of state inside the bloc. You should move all those fields in the state class/es; you should also be able to rebuild your UI using BlocBuilders only without needing setState.
If you're still having issues after that, feel free to comment 馃憤

Thanks @RollyPeres for taking the time to answer!

@hackerunet closing for now but feel free to comment with additional questions/issues are applying the suggested changes and I'm happy to continue the conversation 馃憤

Was this page helpful?
0 / 5 - 0 ratings