Search code examples
flutterdartflutter-provider

Flutter Item ExpansionPanelList doesn't change state


I am trying to retrieve data from API, that's works nice.

After that I want to show my data in a ExpansionPanelList, which is builded by a method:

class _CartaPageState extends State<CartaPage> {

  
  @override
  Widget build(BuildContext context) {
    // Nos suscribimos al provider
    final productoService = Provider.of<ProductoService>(context);
    final List<Producto> productos = productoService.productos; 
    _productosItems = productosToItem(productos);
    return Scaffold(
      body: Container(
        height: double.infinity,
        width: double.infinity,
        child: ListView(
          children: [
            ExpansionPanelList(
              animationDuration: Duration(milliseconds: 300),
              expansionCallback: (int index, bool isExpanded) {
                setState(() {
                  _productosItems[index].isExpanded = !isExpanded;
                  //productosItems[index].isExpanded = !productosItems[index].isExpanded;
                });
              },
              //children: productosToItem(productoService.entrantes).map<ExpansionPanel>((Item item) {
              children: _productosItems.map<ExpansionPanel>((Item item) {
                return ExpansionPanel(
                  headerBuilder: (context, isExpanded) {
                    return ListTile(
                      title: Text(item.headerValue),
                    );
                  },
      ................

The data is shown perfect, but the state is not refreshing on my ItemModel, I think the problem is because the widget is redrawing each time I touch the panel list, that retrieve (again) data from the API and never changes the state.

How can I resolve it?

Thank you in advance

EDIT: CartaPage is wraped by:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => ProductoService()),
      ],
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        title: 'Material App',
        home: CartaPage()
      ),
    );
  }
}

EDIT 2:

I agree I am losing state, this it the method to convert Product to Item:

List<Item> productosToItem(List<Producto> productos) {
  return List.generate(productos.length, (index) {
    return Item(
      headerValue: productos[index].tipo,
      expandedValue: productos[index].nombre,
    );
  });
}

Solution

  • Is ExpansionPanel having its isExpanded set to item.isExpanded?

    You get your isExpanded state from whatever productosToItem() generates.

    When you call setState you queue a new build, which will call productosToItem() again. Without knowing what that method does, I cannot help much.

    I would suggest you look into productosToItem and why it isn't setting isExpanded to the correct value.

    If _productosItems[index].isExpanded isn't a setter, I would imagine you are losing the state.

    EDIT 1:

    You can create an internal state list that can persist the expanded state:

    class Item {
      Item({
        this.expandedValue,
        this.headerValue,
        this.producto,
        this.isExpanded = false,
      });
    
      String expandedValue;
      String headerValue;
      Producto producto; // <------------- ADDED
      bool isExpanded;
    }
    
    class _CartaPageState extends State<CartaPage> {
      Map<Producto, bool> expanded = {}; // <------------- ADDED
    
      @override
      Widget build(BuildContext context) {
        // Nos suscribimos al provider
        final productoService = Provider.of<ProductoService>(context);
        final List<Producto> productos = productoService.productos;
        // NOTE: ----------- converted to a local variable
        final _productosItems = productosToItem(productos);
        return Scaffold(
          body: Container(
            height: double.infinity,
            width: double.infinity,
            child: ListView(
              children: [
                ExpansionPanelList(
                  key: ValueKey(productos.length),  // <------------- ADDED
                  animationDuration: Duration(milliseconds: 300),
                  expansionCallback: (int index, bool isExpanded) {
                    // NOTE: ----------- updated
                    final producto = productos[index];
                    setState(() {
                      expanded[producto] = !isExpanded;
                    });
                  },
                  children: _productosItems.map<ExpansionPanel>((Item item) {
                    return ExpansionPanel(
                      isExpanded: expanded[item.producto], // <------------- ADDED
                      canTapOnHeader: true,
                      headerBuilder: (context, isExpanded) {
                        return ListTile(
                          title: Text(item.headerValue),
                        );
                      },
                      body: ListTile(
                        title: Text(item.expandedValue),
                      ),
                    );
                  }).toList(),
                ),
              ],
            ),
          ),
        );
      }
    
      List<Item> productosToItem(List<Producto> productos) {
        // keep a list of previous map
        final toRemove = Map<Producto, bool>.from(expanded);
        final items = List.generate(productos.length, (index) {
          final producto = productos[index];
          // set initial expanded state
          expanded.putIfAbsent(producto, () => false);
          // the item will be retained
          toRemove.remove(producto);
          return Item(
            headerValue: producto.tipo,
            expandedValue: producto.nombre,
            producto: producto,
            isExpanded: expanded[producto],
          );
        });
        if (toRemove.isNotEmpty) {
          // cleanup unused items
          expanded.removeWhere((key, _) => toRemove.containsKey(key));
        }
        return items;
      }
    }
    

    The key: ValueKey(productos.length), is needed, since ExpansionPanelList acted weirdly with magically appearing or disappearing items.