首页 > 解决方案 > 如何在 Flutter 中挂载的小部件中设置状态?

问题描述

我是 Flutter 的新手。我制作了一个应用程序,它有一个包含类别、子类别和项目的目录(如您在照片中看到的那样)。我的小部件树是:

Catalogue(cart):{[Category-List, Subcategory-List, Item-List:{ItemRow(ListTile)}]}.

我面临以下问题:我的购物车中有物品,它们的数量显示在目录(物品行)上。当我从购物车中删除一个项目或清除所有项目时,我无法将项目行的文本字段的控制器设置为零,因为该小部件(当前项目行)已安装。我使用 Scoped 模型来添加、删除或更新购物车中的商品。所以,我的问题只是视觉上的。当我单击另一个类别然后转到上一个类别时,控制器已正确设置为零(导致使用 initstate() 再次重新创建项目行)。

我的问题有什么解决办法吗?谢谢!

应用图片: 如此处所示

目录.dart 代码:

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_slidable/flutter_slidable.dart';

import '../widgets/categories/categories_manager.dart';
import '../widgets/subcats/subcat_manager.dart';
import '../widgets/items/items_list.dart';
import '../scoped-models/main.dart';
import '../models/item.dart';

class CataloguePage extends StatefulWidget {
  final String _langSelected;
  CataloguePage(this._langSelected, this.model);
  final MainModel model;

  @override
  State<StatefulWidget> createState() {
    return _CataloguePageState();
  }
}

class _CataloguePageState extends State<CataloguePage> {
  Widget currentPage;
  SubcatManager subcatPage;
  bool _loadingProgress;
  List<Item> _listCart;
  final SlidableController slidableController = SlidableController();

  @override
  void initState() {
    _listCart = widget.model.itemsInCart;

    _loadingProgress = true;
    widget.model
        .fetchCategories(widget.model.serverAddress, widget.model.serverPort)
        .then((bool success) {
      if (success) {
        setState(() {
          widget.model
              .fetchSubcats(widget.model.serverAddress, widget.model.serverPort,
                  widget.model.categories[0].catid)
              .then((bool success2) {
            if (success2) {
              setState(() {
                widget.model
                    .fetchItems(
                        widget.model.serverAddress,
                        widget.model.serverPort,
                        widget.model.categories[0].catid,
                        widget.model.subcats[0].subcatid)
                    .then((bool success3) {
                  if (success3) {
                    _loadingProgress = false;
                  }
                });
              });
            }
          });
        });
      } else {
        showDialog(
            barrierDismissible: true,
            context: context,
            builder: (BuildContext context) {
              return AlertDialog(
                title: Text('An error has occured.'),
                content: Text('Connection with Server failed!'),
                actions: <Widget>[
                  FlatButton(
                    onPressed: () {
                      Navigator.popUntil(
                          context, (_) => !Navigator.canPop(context));
                      Navigator.pushReplacementNamed(context, '/');
                    },
                    child: Text('OK'),
                  )
                ],
              );
            });
      }
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).primaryColor,
        title: Text('Catalogue'),
        actions: <Widget>[
          Stack(
            children: <Widget>[
              IconButton(
                icon: Icon(
                  Icons.shopping_cart,
                  size: 30.0,
                ),
                onPressed: () {
                  showModalBottomSheet(
                    context: context,
                    builder: (BuildContext contex) {
                      return _buildCartList(_listCart);
                    },
                  );
                },
              ),
              widget.model.itemsInCart.length > 0
                  ? CircleAvatar(
                      radius: 10.0,
                      child: Text(widget.model.itemsInCart.length.toString()),
                    )
                  : Container()
            ],
          )
        ],
      ),
      body: _buildBody(),
    );
  }

  Widget _buildBody() {
    if (_loadingProgress) {
      return Container(
        color: Theme.of(context).backgroundColor,
        child: Center(
          child: Theme.of(context).platform == TargetPlatform.iOS
              ? CupertinoActivityIndicator(
                  radius: 20.0,
                )
              : CircularProgressIndicator(
                  strokeWidth: 3.0,
                ),
        ),
      );
    } else {
      return Container(
        padding: EdgeInsets.all(20),
        color: Theme.of(context).backgroundColor,
        child: Row(
          children: <Widget>[
            Flexible(
              flex: 3,
              child: Column(
                children: [
                  Flexible(
                    child: CategoriesManager(widget.model),
                  ),
                ],
              ),
            ),
            widget.model.subcats[0].subcatid == 0
                ? Container()
                : VerticalDivider(
                    color: widget.model.themeBrightness == 1
                        ? Colors.white
                        : Colors.black,
                  ),
            widget.model.subcats[0].subcatid == 0
                ? Container()
                : Flexible(
                    flex: 3,
                    child: Column(
                      children: [
                        Flexible(
                          child: SubcatManager(widget.model),
                        ),
                      ],
                    ),
                  ),
            VerticalDivider(
              color: widget.model.themeBrightness == 1
                  ? Colors.white
                  : Colors.black,
            ),
            Flexible(
              flex: 4,
              child: Column(
                children: [
                  Text('Items'),
                  SizedBox(
                    height: 20.0,
                  ),
                  Flexible(
                    child: ItemList(widget.model),
                  ),
                ],
              ),
            ),
          ],
        ),
      );
    }
  }

  Widget _buildCartList(List<Item> listCart) {
    Widget itemCartCards;
    if (listCart.length > 0) {
      itemCartCards = Padding(
        padding: EdgeInsets.all(20.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: <Widget>[
                Text('Selected Items'),
                Text('Total Quantity: ' +
                    widget.model.cartTotalItems.toString()),
              ],
            ),
            SizedBox(
              height: 10.0,
            ),
            Expanded(
              child: ListView.separated(
                separatorBuilder: (contex, index) => Divider(
                      color: widget.model.themeBrightness == 1
                          ? Colors.white
                          : Colors.black,
                    ),
                itemBuilder: (BuildContext context, index) {
                  return Slidable(
                    key: Key(listCart[index].itemid),
                    controller: slidableController,
                    delegate: SlidableDrawerDelegate(),
                    actionExtentRatio: 0.25,
                    secondaryActions: <Widget>[
                      IconSlideAction(
                        icon: Icons.delete,
                        caption: 'Delete',
                        color: Colors.red,
                        onTap: () {
                          widget.model
                              .deleteItemFromCart(listCart[index].itemid);
                        },
                      )
                    ],
                    child: ListTile(
                      title: Text(
                        listCart[index].itemperi,
                      ),
                      trailing: Row(
                        mainAxisSize: MainAxisSize.min,
                        mainAxisAlignment: MainAxisAlignment.end,
                        children: <Widget>[
                          widget.model.showListItemsPrices
                              ? Text(listCart[index].itemCount.toString() +
                                  ' x ' +
                                  listCart[index].itemprice.toString() +
                                  ' €')
                              : Text(listCart[index].itemCount.toString()),
                        ],
                      ),
                    ),
                  );
                },
                itemCount: listCart.length,
              ),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                RaisedButton(
                  shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(10)),
                  child: Text('Clear Order'),
                  color: Colors.red,
                  onPressed: () {
                    return showDialog<void>(
                      context: context,
                      barrierDismissible: false,
                      builder: (BuildContext context) {
                        return AlertDialog(
                          shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(10.0),
                          ),
                          title: Text('Warning!'),
                          content:
                              Text('Are you sure you want to empty your cart?'),
                          actions: <Widget>[
                            FlatButton(
                                child: Text('Yes'),
                                onPressed: () {
                                  Navigator.of(context).pop();
                                  Navigator.of(context).pop();
                                  widget.model.deleteAllCartItems();
                                }),
                            FlatButton(
                              child: Text('No'),
                              onPressed: () {
                                Navigator.of(context).pop();
                              },
                            ),
                          ],
                        );
                      },
                    );
                  },
                ),
                SizedBox(
                  width: 20.0,
                ),
                RaisedButton(
                  shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(10)),
                  child: Text('Confirm Order'),
                  color: Colors.green,
                  onPressed: () {
                    _buildJsonOrder();
                  },
                ),
              ],
            ),
          ],
        ),
      );
    } else {
      itemCartCards = Container(
        child: Center(
          child: Text('Your cart is empty.'),
        ),
      );
    }
    return itemCartCards;
  }

  void _buildJsonOrder() {
    final List<dynamic> _listItems = [];
    for (Item item in widget.model.itemsInCart) {
      final Map<String, dynamic> itemData = {
        'hallid': [widget.model.hallNumber],
        'tableid': [widget.model.tableNumber],
        'itemid': ['${item.itemid}'],
        'itemperi': ['${item.itemperi}'],
        'kind': [0],
        'catid': [item.itemCatId],
        'subcatid': [item.itemSubcatId],
        'quantity': [item.itemCount],
        'price': [item.itemprice]
      };
      _listItems.add(itemData);
    }

    final Map<String, dynamic> orderData = {
      'hallid': [widget.model.hallNumber],
      'tableid': [widget.model.tableNumber],
      'typeofpos': ['4'],
      'posid': [600],
      'userid': [widget.model.currentUserId],
      'items': _listItems
    };

    widget.model
        .sendOrder(
            widget.model.serverAddress, widget.model.serverPort, orderData)
        .then((bool success) {
      if (success) {
        showDialog(
            barrierDismissible: false,
            context: context,
            builder: (BuildContext context) {
              return AlertDialog(
                title: Text('Success.'),
                content: Text('Your order has been placed successfully!'),
                actions: <Widget>[
                  FlatButton(
                    onPressed: () {
                      widget.model.deleteAllCartItems();
                      Navigator.pop(context);
                      Navigator.of(context).pop();
                    },
                    child: Text('OK'),
                  )
                ],
              );
            });
      } else {
        showDialog(
            barrierDismissible: false,
            context: context,
            builder: (BuildContext context) {
              return AlertDialog(
                title: Text('An error has occured.'),
                content: Text('Something went wrong with your order.'),
                actions: <Widget>[
                  FlatButton(
                    onPressed: () {
                      Navigator.pop(context);
                    },
                    child: Text('OK'),
                  )
                ],
              );
            });
      }
    });
  }
}

ItemList.dart 代码:

import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';

import '../../models/item.dart';
import '../../scoped-models/main.dart';
import './item_row.dart';

class ItemList extends StatefulWidget {
  final MainModel model;
  ItemList(this.model);
  @override
  State<StatefulWidget> createState() {
    return _ItemListState();
  }
}

class _ItemListState extends State<ItemList> {
  @override
  void initState() {
    super.initState();
  }

  Widget _buildItemList(List<Item> items) {
    Widget itemCards;
    if (items.length > 0) {
      itemCards = ListView.separated(
        separatorBuilder: (contex, index) => Divider(
              color: widget.model.themeBrightness == 1
                  ? Colors.white
                  : Colors.black,
            ),
        itemBuilder: (BuildContext context, index) {
          return ItemRow(widget.model, items[index]);
        },
        itemCount: items.length,
      );
    } else {
      itemCards = Container();
    }
    return itemCards;
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        Expanded(
          child: ScopedModelDescendant<MainModel>(
            builder: (BuildContext context, Widget child, MainModel model) {
              return _buildItemList(model.items);
            },
          ),
        ),
      ],
    );
  }
}

ItemRow.dart 代码:

import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';

import '../../models/item.dart';
import '../../scoped-models/main.dart';

class ItemRow extends StatefulWidget {
  final MainModel model;
  final Item item;
  ItemRow(this.model, this.item);

  @override
  State<StatefulWidget> createState() {
    return _ItemRowState();
  }
}

class _ItemRowState extends State<ItemRow> {
  int _itemCount;
  TextEditingController _itemCountController;

  @override
  void initState() {
    setState(() {
      _itemCount = widget.item.itemCount;
      _itemCountController = TextEditingController(text: _itemCount.toString());
    });
    super.initState();
  }

  @override
  void didUpdateWidget(ItemRow oldWidget) {
    if (oldWidget.item.itemid != widget.item.itemid) {
      setState(() {
        _itemCount = widget.item.itemCount;
        _itemCountController =
            TextEditingController(text: _itemCount.toString());
      });
    }
    super.didUpdateWidget(oldWidget);
  }

  @override
  Widget build(BuildContext context) {
    return ScopedModelDescendant<MainModel>(
      builder: (BuildContext context, Widget child, MainModel model) {
        return _buildItem(widget.item);
      },
    );
  }

  Widget _buildItem(Item item) {
    return ListTile(
      leading: CircleAvatar(
          backgroundImage: item.itemimage == ''
              ? AssetImage('assets/noimage.png')
              : NetworkImage(item.itemimage)),
      title: Text(
        item.itemperi,
      ),
      trailing: Row(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          widget.model.showListItemsPrices
              ? Text(
                  item.itemprice.toString() + ' €',
                )
              : Container(),
          widget.model.showListItemsCart ? VerticalDivider() : Container(),
          widget.model.showListItemsCart
              ? _buildListItemCart(item)
              : Container()
        ],
      ),
      onTap: () {
        if (widget.model.clickItems) {
          Navigator.pushNamed(context, '/item/' + item.itemid);
        }
      },
    );
  }

  Widget _buildListItemCart(Item item) {
    return Container(
      child: Row(
        children: <Widget>[
          GestureDetector(
            onLongPress: () {
              if (_itemCount != 0) {
                setState(() {
                  _itemCount = 0;
                  _itemCountController =
                      TextEditingController(text: _itemCount.toString());
                  widget.model.deleteItemFromCart(item.itemid);
                });
              }
            },
            child: IconButton(
              icon: Icon(Icons.remove),
              onPressed: () {
                if (_itemCount != 0) {
                  setState(() {
                    _itemCount--;
                    _itemCountController =
                        TextEditingController(text: _itemCount.toString());
                    if (_itemCount == 0) {
                      widget.model.deleteItemFromCart(item.itemid);
                    } else {
                      widget.model.updateItemCart(item.itemid, _itemCount);
                    }
                  });
                }
              },
            ),
          ),
          Container(
            width: 30.0,
            child: TextField(
              decoration: InputDecoration(border: InputBorder.none),
              textAlign: TextAlign.center,
              controller: _itemCountController,
              keyboardType: TextInputType.numberWithOptions(),
              onTap: () {
                _itemCountController.selection = TextSelection(
                    baseOffset: 0,
                    extentOffset: _itemCountController.text.length);
              },
              onSubmitted: (value) {
                if (value == 0.toString()) {
                  widget.model.deleteItemFromCart(item.itemid);
                }
                if (value != 0.toString() && _itemCount == 0) {
                  widget.model.addItemToCart(
                      item.itemid,
                      item.itemperi,
                      item.itemprice,
                      int.parse(value),
                      int.parse(widget.model.selectedCatid),
                      int.parse(widget.model.selectedSubcatId));
                }
                if (value != 0.toString() && _itemCount != 0) {
                  widget.model.updateItemCart(item.itemid, int.parse(value));
                }
                _itemCount = int.parse(value);
                TextEditingController(text: _itemCount.toString());
              },
            ),
          ),
          IconButton(
            icon: Icon(Icons.add),
            onPressed: () {
              setState(
                () {
                  _itemCount++;
                  _itemCountController =
                      TextEditingController(text: _itemCount.toString());
                  if (_itemCount == 1) {
                    widget.model.addItemToCart(
                        item.itemid,
                        item.itemperi,
                        item.itemprice,
                        _itemCount,
                        int.parse(widget.model.selectedCatid),
                        int.parse(widget.model.selectedSubcatId));
                  } else {
                    widget.model.updateItemCart(item.itemid, _itemCount);
                  }
                },
              );
            },
          ),
        ],
      ),
    );
  }
}

ScopedModelItems.dart 代码:

import 'package:scoped_model/scoped_model.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

import '../models/item.dart';
import '../scoped-models/main.dart';

mixin ItemsModel on Model {
  MainModel model;
  List<Item> _items = [];
  List<Item> itemsEmpty = [];
  int _cartTotalItems = 0;
  List<Item> _itemsCartEmpty = [];
  Item _itemDetails;

  Item get itemDetails {
    return _itemDetails;
  }

  List<Item> get items {
    return List.of(_items);
  }

  List<Item> get itemsInCart {
    return _itemsCartEmpty;
  }

  int get cartTotalItems {
    if (_cartTotalItems == null) {
      _cartTotalItems = 0;
    }
    return _cartTotalItems;
  }

  void initState() {
    _items.forEach(_addItem);
  }

  void _addItem(Item item) {
    itemsEmpty.add(item);
  }

  void addItemToCart(String itemid, String itemperi, num itemprice,
      int itemQuantity, int itemCatid, int itemSubcatid) {
    Item item = Item(
        itemid: itemid,
        itemperi: itemperi,
        itemprice: itemprice,
        itemCount: itemQuantity,
        itemCatId: itemCatid,
        itemSubcatId: itemSubcatid);
    _itemsCartEmpty.add(item);
    _cartTotalItems += itemQuantity;
    notifyListeners();
  }

  void updateItemCart(String itemid, int itemQuantity) {
    _itemsCartEmpty.forEach((item) {
      if (item.itemid == itemid) {
        _cartTotalItems -= item.itemCount;
        item.itemCount = itemQuantity;
        _cartTotalItems += itemQuantity;
      }
    });
    notifyListeners();
  }

  void deleteItemFromCart(String itemid) {
    _itemsCartEmpty.forEach((item) {
      if (item.itemid == itemid) {
        _cartTotalItems -= item.itemCount;
      }
    });
    _itemsCartEmpty.removeWhere((item) => item.itemid == itemid);
    notifyListeners();
  }

  void deleteAllCartItems() {
    _itemsCartEmpty.removeRange(0, _itemsCartEmpty.length);
    _cartTotalItems = 0;
    notifyListeners();
  }

  Future<bool> fetchItems(String serverAddress, String serverPort,
      dynamic catid, dynamic subcatid) {
    return http.get(
        'http://$serverAddress:$serverPort/cats/$catid/subcats/$subcatid/items/GR',
        headers: {'Accept': 'application/json'}).then((http.Response response) {
      if (response.statusCode == 200 || response.statusCode == 201) {
        final List<Item> fetchedItemList = [];
        final List<dynamic> itemListData = json.decode(response.body);
        itemListData.forEach((dynamic itemData) {
          String imageData = '', periData = '';
          if (itemData['item_image_path'] != '') {
            imageData = 'http://$serverAddress:$serverPort/photos/' +
                itemData['item_image_path'];
          }
          if (itemData['item_webperi'] == '') {
            periData = itemData['item_peri'];
          } else {
            periData = itemData['item_webperi'];
          }
          final Item item = Item(
              itemid: itemData['item_id'],
              itemperi: periData,
              itemimage: imageData,
              itemprice: itemData['item_price'],
              itemCount: 0);
          if (_itemsCartEmpty.isNotEmpty) {
            for (Item itemCart in _itemsCartEmpty) {
              if (itemCart.itemid == item.itemid) {
                item.itemCount = itemCart.itemCount;
              }
            }
          }
          fetchedItemList.add(item);
        });
        _items = fetchedItemList;
        notifyListeners();
        return true;
      } else {
        return false;
      }
    }).catchError((error) {
      print(error);
      notifyListeners();
      return false;
    });
  }
}

标签: dartfluttersetstate

解决方案


由于没有代码,我将仅列出使用范围模型时导致此问题的常见错误:

1.没有调用notify

您必须在作用域模型中调用 notifyListeners() 来更新使用您的属性的 UI。

2.不告诉模型重建

如果您没有使用 ScopedModelDescendent 小部件,而是使用

appModel = ScopedModel.of<AppModel>(context);

将您的代码更改为

appModel = ScopedModel.of<AppModel>(context, rebuildOnChange: true);

如果您同时执行这两项操作,请发布您的 UI 和范围模型的代码,以便更轻松地提供我们的帮助。


推荐阅读