【问题标题】:Flutter BLoC - state doesn't triggers widget rebuildFlutter BLoC - 状态不会触发小部件重建
【发布时间】: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 下方。导航动画将再次触发。

标签: flutter bloc state-management


【解决方案1】:

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

【讨论】:

    猜你喜欢
    • 2021-03-10
    • 2020-07-05
    • 2020-06-17
    • 2021-06-17
    • 2020-03-10
    • 2022-01-21
    • 1970-01-01
    • 1970-01-01
    • 2020-02-21
    相关资源
    最近更新 更多