Flutter:调用 setState() 方法后不显示 SnackBar

Posted

技术标签:

【中文标题】Flutter:调用 setState() 方法后不显示 SnackBar【英文标题】:Flutter: SnackBar not displayed after calling setState() method 【发布时间】:2021-04-06 00:49:05 【问题描述】:

我正在实现这个产品应用程序,用户可以在其中从他的愿望清单中添加产品并将其添加到他们的购物车中。一旦用户单击添加到购物车按钮,我想从屏幕上删除产品并显示“成功”Snackbar。 由于产品是从 FireBase Firestore 加载和显示的,因此我删除了该项目并调用 setState(); 以便更新屏幕上的列表。问题是没有显示紧跟在setState(); 之后的SnackBar

我认为这是因为小部件树已重建,因此当前状态和上下文“已消失”。 我尝试在网上查找一些信息,但没有找到任何有用的信息。我还尝试使用bool 标志进行解决,一旦用户使用setState 单击“添加到购物车”按钮,该标志将设置为真,以便在小部件时显示SnackBar树已重建,然后将标志重新关闭,但效果不佳。

我错过了什么?调用setState()后如何显示SnackBar

这是我的代码:(问题行标有FIXME:

slidable package installation

  final GlobalKey _repaintBoundaryKey = GlobalKey();
  final GlobalKey<ScaffoldState> _scaffoldKeyWishList = new GlobalKey<ScaffoldState>();
  final Center _circularProgressIndicator = Center(
    child: SizedBox(
        width: 60,
        height: 60,
        child: CircularProgressIndicator(
          valueColor: new AlwaysStoppedAnimation<Color>(Colors.lightGreen[800]),
        )
    ),
  );

@override
  Widget build(BuildContext context) 
    return Material(
      child: FutureBuilder(
        future: FirebaseFirestore.instance.collection("Wishlists").doc(FirebaseAuth.instance.currentUser.uid).get(),
        builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> wishListSnapshot) 
          if (wishListSnapshot.connectionState != ConnectionState.done) 
            return _circularProgressIndicator;
           else if (!wishListSnapshot.hasData ||
              0 == wishListSnapshot.data.data()['Wishlist'].length) 
            return globals.emptyListErrorScreen(context, 'Wishlist');
          
          int totalProducts = wishListSnapshot.data.data()['Wishlist'].length;
          return Scaffold(
            key: _scaffoldKeyWishList,
            backgroundColor: Colors.lightGreen[800],
            body: SingleChildScrollView(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  ClipRRect(
                      borderRadius: BorderRadius.only(
                        topLeft: Radius.circular(20.0),
                        topRight: Radius.circular(20.0),
                      ),
                      child: Container(
                        width: MediaQuery.of(context).size.width,
                        height: MediaQuery.of(context).size.height,
                        color: Colors.white,
                        child: ListView.builder(
                          itemCount: totalProducts * 2,
                          shrinkWrap: true,
                          padding: const EdgeInsets.all(16),
                          itemBuilder: (BuildContext _context, int i) 
                            if (i >= 2 * totalProducts) 
                              return null;
                            
                            if (i.isOdd) 
                              return Divider(
                                color: Colors.green,
                                thickness: 1.0,
                              );
                            
                            var wishlistIdData = wishListSnapshot.data.data()['Wishlist'];
                            String productID = wishlistIdData[i ~/ 2];
                            return FutureBuilder(
                              future: FirebaseFirestore.instance.collection("Products").doc(productID).get(),
                              builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> productSnapshot) 
                                if (wishListSnapshot.connectionState != ConnectionState.done || !productSnapshot.hasData) 
                                  return _circularProgressIndicator;
                                
                                var productData = productSnapshot.data.data()['Product'];
                                String prodName = productData['name'];
                                String prodPrice = productData['price'];
                                String prodDate = productData['date'];
                                return Slidable(
                                  actionPane: SlidableDrawerActionPane(),
                                  actionExtentRatio: 0.22,
                                  direction: Axis.horizontal,
                                  actions: <Widget>[
                                    //add to cart
                                    IconSlideAction(
                                      caption: 'Add to cart',
                                      color: Colors.transparent,
                                      foregroundColor: Colors
                                          .amberAccent,
                                      icon: Icons.add_shopping_cart,
                                      onTap: () async 
                                        globals.userCart.add(
                                            globals.Product(
                                                productID,
                                                FirebaseAuth.instance.currentUser.uid,
                                                prodName,
                                                double.parse(prodPrice),
                                                prodDate,
                                                [],
                                                "",
                                                "")
                                        );

                                        ///removing product from wishlist
                                        List toRemove = [];
                                        toRemove.add(productID);
                                        await FirebaseFirestore.instance
                                            .collection('Wishlists')
                                            .doc(FirebaseAuth.instance.currentUser.uid)
                                            .get()
                                            .then((value) async 
                                          List<dynamic> list = List
                                              .from(value
                                              .data()['Wishlist']);
                                          list
                                            ..removeWhere((e) =>
                                                toRemove.contains(e));
                                          await FirebaseFirestore.instance
                                              .collection('Wishlists')
                                              .doc(FirebaseAuth.instance.currentUser.uid)
                                              .update(
                                              'Wishlist': list);
                                        );
                                        setState(() 
                                          ///to update the list on screen
                                        );
                                        //FIXME: snackbar not displayed after setState!
                                        ///showing snackbar upon completion
                                        _scaffoldKeyWishList
                                            .currentState
                                            .showSnackBar(
                                            SnackBar(
                                              content: Text(
                                                'Product Successfully Added to Cart!',
                                                style: GoogleFonts
                                                    .lato(
                                                    fontSize: 13.0,
                                                    color: Colors
                                                        .white
                                                ),
                                              ),
                                              behavior: SnackBarBehavior
                                                  .floating,
                                              action: SnackBarAction(
                                                label: 'Checkout',
                                                textColor: Colors
                                                    .lime,
                                                onPressed: () =>
                                                    showDialog(
                                                      context: context,
                                                      builder: (
                                                          BuildContext context) 
                                                        return CustomDialogBox();
                                                      ,
                                                    ),
                                              ),
                                            )
                                        );
                                      ,
                                    ),
                                  ],
                                  child: ListTile(
                                    title: Text(prodName,
                                      style: GoogleFonts.lato(
                                        fontSize: 18.0,
                                        color: Colors.black,
                                      ),
                                    ),
                                    subtitle: Text(prodPrice + "\$",
                                      style: GoogleFonts.lato(
                                        fontSize: 13.5,
                                        color: Colors.grey,
                                      ),
                                    ),
                                    visualDensity: VisualDensity
                                        .adaptivePlatformDensity,
                                  ),
                                );
                              ,
                            );
                          ,
                        ),
                      )
                  )
                ]
              )
            )
          );
        
      )
    );
  

【问题讨论】:

【参考方案1】:

在我的例子中,我在 build 方法完成构建小部件的过程之前调用了 setState 方法。

如果您在构建method 完成之前显示snack baralert 对话框以及在许多其他情况下,您可能会遇到此error。所以在这种情况下使用下面的回调函数。

  WidgetsBinding.instance.addPostFrameCallback((_) 
        // add your snackbar code here
      );

或者你也可以使用 SchedulerBinding 来做同样的事情。

SchedulerBinding.instance.addPostFrameCallback((_) 

  // add your code here of snackbar.

);

或者你也可以试试这个(我不确定这个)

if(mounted)
  //add your code here of snackbar

CREDITS

【讨论】:

我想显示SnackBar,因此-使用您的解决方案会导致错误The method 'showSnackBar' was called on null.。我在FIXME 下的代码中显示SnackBar

以上是关于Flutter:调用 setState() 方法后不显示 SnackBar的主要内容,如果未能解决你的问题,请参考以下文章

Flutter的setState更新原理和流程

在 Flutter 中从 main() 调用 setState()

Flutter VideoPlayerController “在构建期间调用了 setState() 或 markNeedsBuild()。”

Flutter - setState不使用新数据

如何在 Flutter 的 StatefulWidget 类中的 setState() 方法上停止小部件重新加载

Flutter - 在构建期间调用 setState() 或 markNeedsBuild()