Search code examples
flutterblocstate-management

Flutter_bloc: ^8.0.0 State does not rebuild when emitting the same state with different props


I am experimenting with state management prototypes and I stumbled upon a problem recently. The problem is that one interaction does reload the screen, which is good. While another interaction does not reload the screen, even though it is emitting the same state (also with a changed prop).

My prototype has 3 screens:

  1. A listview (https://i.sstatic.net/bI9K6.png)
  2. A detailpage of the listitem (https://i.sstatic.net/DByDn.png)
  3. Form (https://i.sstatic.net/s8ltv.png)

The stars in the screenshot are represented in the widget below:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:state_management/models/product/product.dart';
import 'package:state_management/models/product/product_bloc.dart';
class RatingBox extends StatefulWidget {
    final Product item;
    RatingBox({
        Key ? key, required this.item
    }): super(key: key);@
    override
    _RatingBoxState createState() {
        return _RatingBoxState();
    }
}
class _RatingBoxState extends State < RatingBox > {@
    override
    Widget build(BuildContext context) {
        double _size = 20;
        return Row(
            mainAxisAlignment: MainAxisAlignment.end,
            crossAxisAlignment: CrossAxisAlignment.end,
            mainAxisSize: MainAxisSize.max,
            children: [
                for (int i = 1; i <= 3; i++)
                    Container(
                        padding: const EdgeInsets.all(0),
                        child: IconButton(
                            icon: (widget.item.rating >= i ? Icon(
                                Icons.star,
                                size: _size,
                            ) : Icon(
                                Icons.star_border,
                                size: _size,
                            )),
                            color: Colors.red[500],
                            onPressed: () {
                                context.read < ProductBloc > ().add(UpdateProductRating(
                                    product: widget.item.copyWith(rating: i)));
                            },
                            iconSize: _size,
                        ),
                    ),
            ],
        );
    }
}

The form is updated through a submit button, using another event(due to test purposes). But it has the same code within it:

context.read < ProductBloc > ().add(UpdateProduct(
    product: item.copyWith(
        name: item.name,
        description: item.description,
        price: item.price)));

The code below is the code of product_bloc, product_event and product_state:

class ProductBloc extends Bloc < ProductEvent, ProductState > {
    ProductBloc(): super(ProductLoading()) {
        on < LoadProduct > (_onLoadProduct);
        on < UpdateProduct > (_onUpdateProduct);
        on < UpdateProductRating > (_onUpdateProductRating);
    }
    void _onLoadProduct(LoadProduct event, Emitter < ProductState > emit) {
        print('load');
        emit(
            ProductLoaded(products: event.products),
        );
    }
    void _onUpdateProductRating(
        UpdateProductRating event, Emitter < ProductState > emit) {
        final state = this.state;
        if (state is ProductLoaded) {
            print('updateRating');
            List < Product > products = (state.products.map((product) {
                return product.id == event.product.id ? event.product : product;
            })).toList();
            emit(ProductLoaded(products: List.of(products)));
        }
    }
    void _onUpdateProduct(UpdateProduct event, Emitter < ProductState > emit) {
        final state = this.state;
        if (state is ProductLoaded) {
            print('update');
            List < Product > products = (state.products.map((product) {
                return product.id == event.product.id ? event.product : product;
            })).toList();
            emit(ProductLoaded(products: products));
        }
    }
}
abstract class ProductEvent extends Equatable {
    const ProductEvent();@
    override
    List < Object > get props = > [];
}
class LoadProduct extends ProductEvent {
    final List < Product > products;
    const LoadProduct({
        this.products = const < Product > []
    });@
    override
    List < Object > get props = > [products];
}
class UpdateProduct extends ProductEvent {
    final Product product;
    const UpdateProduct({
        required this.product
    });@
    override
    List < Object > get props = > [product];
}
class UpdateProductRating extends ProductEvent {
    final Product product;
    const UpdateProductRating({
        required this.product
    });@
    override
    List < Object > get props = > [product];
}
abstract class ProductState extends Equatable {
    const ProductState();@
    override
    List < Object > get props = > [];
}
class ProductLoading extends ProductState {}
class ProductLoaded extends ProductState {
    final List < Product > products;
    const ProductLoaded({
        this.products = const < Product > []
    });@
    override
    List < Object > get props = > [products];
}

The most important part is that setting the stars will while changing the form on the next page does not do the trick. The weird thing is that it will change, if I emit another state before emitting the state I want the app to be in.

I hope this makes sense to you all. It is my first post on StackOverflow, so I would appreciate feedback!


Solution

  • I have found the issue!

    I set my item properties too early, which leads to the fact that BLoC does not recognize the change, since it is the same product that is being set.

    So instead of setting item.xx in the onChange event, try to store the result on another place such as a TextEditingController. Then when the form is submitted, use the stored data to create a new object, which will get recognized as a change.