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 - 在有状态小部件与无状态小部件中的使用