Solucionado (ver solução)
Solucionado
(ver solução)
2
respostas

[Dúvida] Múltiplos widgets com botões e exibição de valores em tela

Inspirado no artigo, decidi criar um app em que fosse possível adicionar múltiplos itens mais quantidade num cesto de compras e exibi-las em tela, mas sem sucesso.

class CartApp extends StatelessWidget {
  const CartApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Cart App",
      home: Scaffold(appBar: AppBar(title: Text("CartApp")), body: ItemList()),
    );
  }
}

class ItemList extends StatefulWidget {
  const ItemList({super.key});

  @override
  State<ItemList> createState() => ItemListState();
}

class ItemListState extends State<ItemList> {
  CartData? cartData;

  add(String name) =>
    cartData!.add(name);

  remove(String name) =>
    cartData!.remove(name);

  @override
  Widget build(BuildContext context) {
    cartData = CartData();
    return CartProvider(
      cartData!,
      child: Column(
        children: [
          Item("Item A", parentState: this),
          Item("Item B", parentState: this),
          CartInfo(),
        ],
      ),
    );
  }
}

class Item extends StatelessWidget {
  final String name;
  final ItemListState parentState;

  const Item(this.name, {super.key, required this.parentState});

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Text(name),
        ElevatedButton(
          onPressed: () => parentState.add(name),
          child: Text("+"),
        ),
        ElevatedButton(
          onPressed: () => parentState.remove(name),
          child: Text("-"),
        ),
      ],
    );
  }
}

class CartInfo extends StatelessWidget {
  const CartInfo({super.key});

  @override
  Widget build(BuildContext context) {
    var cartData = CartProvider.of(context).cartData;
    return Center(child: Text("Data=$cartData"));
  }
}

class CartProvider extends InheritedWidget {
  final CartData cartData;

  const CartProvider(this.cartData, {super.key, required super.child});

  @override
  bool updateShouldNotify(CartProvider oldWidget) =>
      oldWidget.cartData != cartData;

  static CartProvider of(BuildContext context) =>
      context.dependOnInheritedWidgetOfExactType<CartProvider>() ??
      CartProvider(CartData(), child: Placeholder());
}

class CartData {
  final Map<String, int> itemsCount = {};

  add(String name) {
    itemsCount[name] = (itemsCount[name] ?? 0) + 1;
    print(this);
  }

  remove(String name) {
    if ((itemsCount[name] ?? 0) <= 1) {
      itemsCount.remove(name);
    } else {
      itemsCount[name] = (itemsCount[name] ?? 0) - 1;
    }
    print(this);
  }

  @override
  String toString() {
    var s = "";
    for (var itemCount in itemsCount.entries) {
      s += "{${itemCount.key}: ${itemCount.value}}";
    }
    return s;
  }

  @override
  bool operator ==(Object other) {
    return (other.runtimeType == CartData)
        ? mapEquals(itemsCount, (other as CartData).itemsCount)
        : false;
  }

  @override
  int get hashCode => itemsCount.hashCode;
} 

Tela do app

Como resolver?

2 respostas
solução!

Olá, Ian, como vai?

A principal questão está na atualização do estado quando um item é adicionado ou removido. Uma abordagem para resolver isso é criar uma nova instância de CartData sempre que houver uma alteração. Você deve modificar a classe ItemListState da seguinte forma:

class ItemListState extends State<ItemList> {
  late CartData cartData;

  @override
  void initState() {
    super.initState();
    cartData = CartData();
  }

  void add(String name) {
    setState(() {
      cartData = cartData.copyWithUpdatedItem(name, 1); // Agora criamos uma nova instância de CartData
    });
  }

  void remove(String name) {
    setState(() {
      cartData = cartData.copyWithUpdatedItem(name, -1); // Mesma lógica para remoção
    });
  }

  @override
  Widget build(BuildContext context) {
    return CartProvider(
      cartData,
      child: Column(
        children: [
          Item("Item A", parentState: this),
          Item("Item B", parentState: this),
          const CartInfo(),
        ],
      ),
    );
  }
}

Além disso, será necessário adicionar o método copyWithUpdatedItem dentro de CartData para garantir que o estado seja atualizado corretamente:

class CartData {
  final Map<String, int> itemsCount;

  CartData({Map<String, int>? itemsCount}) : itemsCount = itemsCount ?? {};

  // Novo método para atualizar o estado corretamente
  CartData copyWithUpdatedItem(String name, int quantityChange) {
    final newItemsCount = Map<String, int>.from(itemsCount);

    if (!newItemsCount.containsKey(name)) {
      newItemsCount[name] = 0;
    }
    newItemsCount[name] = (newItemsCount[name]! + quantityChange).clamp(0, double.infinity).toInt();

    if (newItemsCount[name] == 0) {
      newItemsCount.remove(name);
    }

    return CartData(itemsCount: newItemsCount);
  }

  @override
  String toString() {
    return itemsCount.entries.map((e) => "{${e.key}: ${e.value}}").join(", ");
  }

  @override
  bool operator ==(Object other) {
    return other is CartData && mapEquals(itemsCount, other.itemsCount);
  }

  @override
  int get hashCode => itemsCount.hashCode;
}

Com essas mudanças, o CartProvider conseguirá detectar alterações e atualizar corretamente os widgets que dependem do estado do carrinho.

Espero ter ajudado!

Siga firme nos seus estudos e conte com o fórum sempre que precisar.

Abraços :)

Caso este post tenha lhe ajudado, por favor, marcar como solucionado

@Mike, obrigado pela ajuda!

Descobri o ofensor através do seu post: estava inicializando a variável cartData no método build() da classe ListItemState, resultando no reset da variável:

@override
  Widget build(BuildContext context) {
    cartData = CartData({}); // <-- Ofensor
    return CartProvider(
      cartData,
      child: Column(
        children: [
          Item("Item A", parentState: this),
          Item("Item B", parentState: this),
          CartInfo(),
        ],
      ),
    );
  }

Utilizei o método initState da sua solução para inicializar e funcionou! Obrigado.