bloc listener only gets called with initial state but not with state change

Created on 9 Sep 2020  路  5Comments  路  Source: felangel/bloc

Hi,
I am following your todo app tutorial to implement flutter bloc. I am using bloc listener to listen to the state change. but it only listens to the initial state and does not show changes based on state change. would you please tell me what is the mistake in the following code. i have tried and searched to find a solution but not a clear one.
this is where I am providing the bloc in my home screen:
List _pages = [
MultiBlocProvider(
child: ProductPage(),
providers: [
BlocProvider(
create: (context) => ProductListBloc()..add(ProductLoaded()),
),
BlocProvider(
create: (context) => CartBloc()..add(CartLoaded()),
),
]
),
//ProductPage(), // all products
CartPage(), // all carts
ProfilePage(), // profile page
];
This is my ProductPage:
class ProductPage extends StatefulWidget {
@override
_ProductPageState createState() => _ProductPageState();
}

class _ProductPageState extends State {
int x = 1;
List products = [];
List cartItems = [];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Products"),
),
body: BlocListener(
listener: (context, state) {
if (state is ProductLoadedSuccess) {
products = state.productList;
}
},
child: BlocBuilder(
builder: (context, state) {
return BlocListener(
listener: (context, state) {
THIS IS BEING CALLED
if (state is CartLoadedSuccess) {
debugPrint('CartLoadedSuccess is triggering');
cartItems = state.cartList;
}
THIS IS NOT BEING CALLED
else if (state is CartAddedFailure) {
debugPrint('CartAddedFailure is triggering');
}
},
child: BlocBuilder(
builder: (context, state) {
// debugPrint('cartItems after listen $cartItems');
// debugPrint('products after listen $products');

                return Container(
                  color: Color(0xf8f8ff),
                  padding: EdgeInsets.all(10),
                  child: buildProductListing(products, context, cartItems),
                );
              },
            ),
          );
        },
      ),
    ));

}

ListView buildProductListing(List products, BuildContext context,
List cartItems) {
return new ListView.builder(
itemCount: products.length,
itemBuilder: (BuildContext ctxt, int index) {
CartDbModel cartProd = cartItems.firstWhere(
(cart) => cart.id == products[index].id,
orElse: () => CartDbModel());
//debugPrint('cartProd $cartProd');
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
child: new ListTile(
title: Text(products[index].name),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.only(top: 5),
child: Row(
children: [
Container(
width: MediaQuery.of(context).size.width / 1.5,
child: Text(
products[index].description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),

                    GestureDetector(
                        onTap: () => debugPrint('remove'),
                        child: Icon(Icons.remove, color: Colors.black)),
                    Text(cartProd.id == null ? '0' : cartProd.dbQty),
                    GestureDetector(
                        onTap: () {
                       THIS IS WHERE I AM TRYING TO MUTATE THE STATE
                          CartBloc()..add(CartAdded(products[index]));
                        },
                        child: Icon(Icons.add, color: Colors.black)),
                  ],
                ),
              ),
              Container(
                margin: EdgeInsets.only(top: 5),
                child: Text(
                  'MYR ${products[index].price.toString()}',
                  style: TextStyle(
                      color: Colors.purple, fontWeight: FontWeight.bold),
                ),
              )
            ],
          ),
        ),
      );
    });

}
}
this is the bloc
class CartBloc extends Bloc {
// final int id;
// final Products product;
// final List carts;
DatabaseCart databaseCart = DatabaseCart();
List cartProducts = [];
CartDbModel cartItem = CartDbModel();
CartBloc() : super(CartLoadedSuccess());

@override
Stream mapEventToState(CartEvent event) async* {
// debugPrint(currentState)
if (event is CartLoaded) {
yield* _mapCartLoadedState();
} else if (event is CartAdded) {
debugPrint('state CartAdded${state}');
yield* _mapCartAddedState(event);
}else if(event is CartAddFailed){
debugPrint('this is being called for fail');
yield* _mapCartAddeFailedState();
} else {
throw UnimplementedError();
}
}

Stream _mapCartAddeFailedState() async* {
yield CartLoadedFailure();
yield CartAddedFailure();
}

Stream _mapCartLoadedState() async* {
try {
databaseCart.getAllCarts().then((value) {
// if (value != null) {
// print('bloc get all: $value');
value.forEach((element) {
// print('cart element ${element.id}');

      cartProducts.add(element);
    });
    // }
  });
  yield CartLoadedSuccess(cartProducts);
} catch (_) {
  yield CartLoadedFailure();
}

}

Stream _mapCartAddedState(CartEvent event) async* {
//print('added state ${event.props[0]}');
Products product = event.props[0];
//print(product.id);
databaseCart.getAllCarts().then((value) {
// debugPrint('bloc add to cart value: $value');
cartItem.id = product.id;
cartItem.name = product.name;
cartItem.availableQuantity = product.availableQuantity;
cartItem.price = product.price;
cartItem.description = product.description;
debugPrint('value length check ${value.length}');
if (value.length > 0) {
List findCart =
value.where((element) => element.id == product.id).toList();
// debugPrint('findcart ${findCart.length}');
if (findCart.length > 0) {
cartItem.dbQty = (int.parse(findCart[0].dbQty) + 1).toString();
if (int.parse(cartItem.dbQty) <= product.availableQuantity) {
if (product.availableQuantity > 0) {
databaseCart
.updateCart(cartItem)
.then((value) =>
//_mapCartLoadedState()
CartBloc()..add(CartLoaded()));
}
}
} else {
cartItem.dbQty = "1";
databaseCart
.saveToCart(cartItem)
.then((value) =>
// _mapCartLoadedState())
CartBloc()..add(CartLoaded()));
}
} else {
if (product.availableQuantity > 0) {
cartItem.dbQty = "1";
databaseCart
.saveToCart(cartItem)
.then((value) =>
// _mapCartLoadedState()
CartBloc()..add(CartLoaded()));
}else{
debugPrint('it is checking 0 items');
CartBloc()..add(CartAddFailed());
}

    // debugPrint('db quantity: ${int.parse(cartItem.dbQty)}');
  }
  //cartProducts = value == null ? [] : value;
});

}
}
this is the event class:

abstract class CartEvent extends Equatable {
const CartEvent();

@override
List get props => [];
}

class CartLoaded extends CartEvent {
}

class CartAddFailed extends CartEvent{

}

class CartAdded extends CartEvent {
final Products product;

const CartAdded( this.product);

@override
List get props => [product];

@override
String toString() => 'Product to add updatedCart: $product ';

}
this is the state class:
abstract class CartState extends Equatable{
const CartState();
@override
List get props => [];
}

class CartLoadedInProgress extends CartState{}

class CartLoadedSuccess extends CartState{
final List cartList;
const CartLoadedSuccess([this.cartList = const []]);
@override
List get props => [cartList];

// @override
// String toString() => 'CartLoadedSuccess { carts state: $cartList }';
}

class CartAddedSuccess extends CartState{}
class CartAddedFailure extends CartState{}
class CartLoadedFailure extends CartState{
@override
String toString() => 'Cart cannot be loaded';
}

question

All 5 comments

Hi @ZAT90 馃憢
Thanks for opening an issue!

Have you taken a look at the FAQs? You should not be maintaining internal state in your bloc (cartProducts) because you are just mutating the same list of products and yielding it which results in the bloc ignoring the state (since it is equal to the previous state).

You should also provide a type for the final List cartList; because right now it's inferred to be List<dynamic> which is not Equatable.

Instead prefer to have something like:

final List<Product> cartList;

Where the class Product extends Equatable like:

class Product extends Equatable {
 ...
}

Closing for now but if you're still having trouble can you please share a link to a sample app which illustrates the issue so I can run and debug the issue locally? Thanks! 馃憤

I have made the changes as per you said. Still nothing changed. I will provide a link of the project to you and maybe you can check it out and guide me towards the right direction. Thanks for your constant help brother.

I have solved it. In place of CartBloc()..add(CartAdded(products[index])), i replaced it with blocprovider.of(context).add(CartAdded(products[index])) and it worked. Would you please tell me why this one worked out?

@ZAT90 if you are adding to a new CartBloc then there are no widgets reacting to it so your app won't rebuild any parts of the UI. That's why using BlocProvider to access the existing bloc works 馃憤

Was this page helpful?
0 / 5 - 0 ratings

Related issues

1AlexFix1 picture 1AlexFix1  路  3Comments

nerder picture nerder  路  3Comments

craiglabenz picture craiglabenz  路  3Comments

wheel1992 picture wheel1992  路  3Comments

ricktotec picture ricktotec  路  3Comments