Flutter BLoC - 状态不会触发小部件重建

Posted

技术标签:

【中文标题】Flutter BLoC - 状态不会触发小部件重建【英文标题】:Flutter BLoC - state doesn't triggers widget rebuild 【发布时间】:2021-10-22 13:40:22 【问题描述】:

该应用程序是简单的类别/产品显示,一切正常,除了从类别产品中选择一个产品并滑回产品小部件,状态发生变化,它既不是我创建的状态之一,只是显示一个加载指示器( ProductsWrapper 默认从状态返回)。

所以这里是代码:

产品块:

class ProductBloc extends Bloc<ProductEvent, ProductState> 
  final ProductRepository productRepository;

  ProductBloc(required this.productRepository) : super(ProductsEmpty());

  @override
  Stream<Transition<ProductEvent, ProductState>> transformEvents(
      Stream<ProductEvent> events,
      TransitionFunction<ProductEvent, ProductState> transitionFn) 
    return super.transformEvents(
        events.debounceTime(const Duration(microseconds: 500)), transitionFn);
  

  @override
  Stream<ProductState> mapEventToState(ProductEvent event) async* 
    if (event is FetchProducts) 
      yield* _mapFetchProductsToState(event);
     else if (event is RefreshProducts) 
      yield* _mapRefreshProductsToState(event);
     else if (event is FetchProduct) 
      yield* _mapFetchProductToState(event);
     else if (event is RefreshProduct) 
      yield* _mapRefreshProductToState(event);
    
  

  Stream<ProductState> _mapFetchProductsToState(FetchProducts event) async* 
    try 
      final products =
          (await productRepository.getCategoryProducts(event.categoryId));
      yield ProductsLoaded(products: products.products!);
     catch (_) 
      yield state;
    
  

  Stream<ProductState> _mapRefreshProductsToState(
      RefreshProducts event) async* 
    try 
      final products =
          await productRepository.getCategoryProducts(event.categoryId);
      yield ProductsLoaded(products: products.products!);
      return;
     catch (_) 
      yield state;
    
  

  Stream<ProductState> _mapFetchProductToState(FetchProduct event) async* 
    try 
      final product =
          (await productRepository.getProductDetails(event.productId));
      yield ProductLoaded(product: product);
     catch (e) 
      yield state;
    
  

  Stream<ProductState> _mapRefreshProductToState(RefreshProduct event) async* 
    try 
      final product =
          await productRepository.getProductDetails(event.productId);
      yield ProductLoaded(product: product);
      return;
     catch (_) 
      yield state;
    
  

状态:


abstract class ProductState extends Equatable 
  const ProductState();

  @override
  List<Object?> get props => [];


class ProductsEmpty extends ProductState 

class ProductEmpty extends ProductState 

class ProductLoading extends ProductState 

class ProductsLoading extends ProductState 

class ProductLoaded extends ProductState 
  final Product product;

  const ProductLoaded(required this.product);

  ProductLoaded copyWith(required Product product) 
    return ProductLoaded(product: product);
  

  @override
  List<Object?> get props => [product];
  @override
  String toString() => 'ProductLoaded  product: $product.name';


class ProductsLoaded extends ProductState 
  final List<Product> products;

  const ProductsLoaded(required this.products);

  ProductsLoaded copyWith(required List<Product> products) 
    return ProductsLoaded(products: products);
  

  @override
  List<Object?> get props => [products];
  @override
  String toString() => 'ProductLoaded  products: $products.length';


class ProductError extends ProductState 

ProductRepository(ProductApiService 只是 api,它工作正常):

class ProductRepository 
  final ProductApiService productApiService;
  ProductRepository(ProductApiService? productApiService)
      : productApiService = productApiService ?? ProductApiService();

  Future<Products> getCategoryProducts(int? categoryId) async 
    return productApiService.fetchCategoryProducts(categoryId);
  

  Future<Product> getProductDetails(int? productId) async 
    return productApiService.fetchProductDetails(productId);
  

产品包装:

  final int? categoryId;

  const ProductsWrapper(Key? key, required this.categoryId) : super(key: key);

  @override
  _ProductsWrapperState createState() => _ProductsWrapperState();


class _ProductsWrapperState extends State<ProductsWrapper> 
  final _scrollController = ScrollController();
  final _scrollThreshold = 200;
  Completer _productsRefreshCompleter = new Completer();

  List<Product> products = [];
  GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

  void _onScroll() 
    final maxScroll = _scrollController.position.maxScrollExtent;
    final currentScroll = _scrollController.position.pixels;
    if (maxScroll - currentScroll <= _scrollThreshold) 
      context
          .read<ProductBloc>()
          .add(FetchProducts(categoryId: widget.categoryId!));
    
  

  @override
  void initState() 
    super.initState();
    context
        .read<ProductBloc>()
        .add(FetchProducts(categoryId: widget.categoryId!));
    _scrollController.addListener(_onScroll);
    _productsRefreshCompleter = Completer();
  

  @override
  Widget build(BuildContext context) 
    var size = MediaQuery.of(context).size;
    final double itemHeight = 260;
    final double itemWidth = size.width / 2;

    return Scaffold(
        key: _scaffoldKey,
        body: BlocListener<ProductBloc, ProductState>(
            listener: (context, state) 
              if (state is ProductsLoaded) 
                products = state.products;
                _productsRefreshCompleter.complete();
              
            ,
            child: Container(
                margin: EdgeInsets.all(8.0),
                child: BlocBuilder<ProductBloc, ProductState>(
                    builder: (context, state) 
                  if (state is ProductsLoading) 
                    print('a7a');
                    return Center(
                      child: LoadingIndicator(),
                    );
                  
                  if (state is ProductsLoaded) 
                    products = state.products;
                    if (state.products.isEmpty) 
                      return Center(
                        child: Text("No Products Found in this category"),
                      );
                    

                    return Scaffold(
                      body: SafeArea(
                        child: Container(
                          child: GridView.builder(
                              itemCount: products.length,
                              scrollDirection: Axis.vertical,
                              gridDelegate:
                                  SliverGridDelegateWithFixedCrossAxisCount(
                                      crossAxisCount: 2,
                                      childAspectRatio:
                                          (itemWidth / itemHeight)),
                              itemBuilder: (context, index) => Card(
                                    elevation: 0,
                                    child: InkWell(
                                      onTap: () 
                                        Navigator.of(context).push(
                                            MaterialPageRoute(
                                                builder: (context) =>
                                                    ProductDetailScreen(
                                                        productId:
                                                            products[index]
                                                                .id)));
                                      ,
                                      child: Container(
                                        child: Column(
                                          mainAxisAlignment:
                                              MainAxisAlignment.start,
                                          crossAxisAlignment:
                                              CrossAxisAlignment.center,
                                          children: [
                                            ClipRRect(
                                              child: Image.network(
                                                products[index]
                                                    .image!
                                                    .image
                                                    .toString(),
                                                height: 150,
                                                fit: BoxFit.fitWidth,
                                              ),
                                            ),
                                            Padding(
                                              padding: EdgeInsets.all(8.0),
                                              child: Text(
                                                products[index].name.toString(),
                                                style: TextStyle(
                                                    color: Colors.black,
                                                    fontWeight:
                                                        FontWeight.bold),
                                              ),
                                            ),
                                            Row(
                                              mainAxisAlignment:
                                                  MainAxisAlignment
                                                      .spaceBetween,
                                              children: [
                                                Padding(
                                                  padding: EdgeInsets.all(12.0),
                                                  child: Text(
                                                      '\$$products[index].price.toString()'),
                                                ),
                                                Padding(
                                                  padding: EdgeInsets.only(
                                                      right: 8.0),
                                                  child: CircleAvatar(
                                                    backgroundColor:
                                                        Theme.of(context)
                                                            .primaryColor,
                                                    radius: 10,
                                                    child: IconButton(
                                                      padding: EdgeInsets.zero,
                                                      icon: Icon(
                                                        Icons.add,
                                                        size: 20,
                                                      ),
                                                      color: Colors.white,
                                                      onPressed: () ,
                                                    ),
                                                  ),
                                                )
                                              ],
                                            )
                                          ],
                                        ),
                                      ),
                                    ),
                                  )),
                        ),
                      ),
                    );
                                    
                  return Center(
                    child: LoadingIndicator(strokeWidth: 5.0,),
                  );
                ))));
  

ProductDetailScreen:

class ProductDetailScreen extends StatefulWidget 
  final int? productId;
  const ProductDetailScreen(Key? key, required this.productId)
      : super(key: key);

  @override
  _ProductDetailScreenState createState() => _ProductDetailScreenState();


class _ProductDetailScreenState extends State<ProductDetailScreen> 
  Completer _productRefreshCompleter = new Completer();
  Product product = new Product();
  GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

  @override
  void initState() 
    super.initState();
    context.read<ProductBloc>().add(FetchProduct(productId: widget.productId));
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      key: _scaffoldKey,
      body: BlocListener<ProductBloc, ProductState>(
        listener: (context, state) 
          if (state is ProductLoaded) 
            product = state.product;
            _productRefreshCompleter.complete();
            _productRefreshCompleter = Completer();
          
        ,
        child: Container(
          child: BlocBuilder<ProductBloc, ProductState>(
            builder: (context, state) 
              if (state is ProductLoading) 
                return Center(
                  child: LoadingIndicator(),
                );
              
              if (state is ProductLoaded) 
                return Scaffold(
                  body: SafeArea(
                    child: Container(
                      child: Text(product.name.toString()),
                    ),
                  ),
                );
              
              return Center(
                child: LoadingIndicator(
                  strokeWidth: 5.0,
                ),
              );
            ,
          ),
        ),
      ),
    );
  

感谢任何帮助。

感谢您花时间阅读本文,祝您有美好的一天并保持安全。

【问题讨论】:

肯定有什么东西触发了这个事件。可能是你的 ScrollController 的监听器 @MozesOng 我认为我的解释是 fague。让我解释一下:当使用 Navigator push 从页面 A 更改到页面 B 时,我希望页面 A 被释放并且页面 B 被初始化,这将允许我在页面 A 上执行清理。这里页面 A 不是处置,它被保存在导航器历史中。我希望这能让您了解我所面临的问题,谢谢 当您从页面 A 导航到页面 B 时,页面 A 不会被丢弃。它仍在导航堆栈上。这是正常行为。也许您可以做的是在导航到页面 B 之前调用一个事件? 或者您可以在导航到页面 B 之前使用 Navigator.of(context).pop() 弹出页面 A。然后当您弹出页面 B 时,您可以再次重新渲染页面 A,以使页面 A 看起来仍在页面 B 下方。导航动画将再次触发。 【参考方案1】:

问题是你使用一个集团做两件事。产品列表是一个实体,单个细节是另一个实体。因此,您需要在 blocBuilders 中使用状态的属性。 另外,您不需要任何侦听器和完成器。当状态改变时,bloc 模式会全部刷新。 我创建了一个带有工作解决方案的仓库。 https://github.com/eugenioamato/categoryproducts

【讨论】:

以上是关于Flutter BLoC - 状态不会触发小部件重建的主要内容,如果未能解决你的问题,请参考以下文章

在 Flutter 中使用 BLoC - 在有状态小部件与无状态小部件中的使用

Flutter Bloc 状态管理

Flutter Bloc 不断重建小部件而不改变状态

Flutter Bloc 冲突状态

Flutter Bloc 不会改变 TextFormField 的初始值

冻结的 BLoC - 产量不会向小部件发出状态更改