Search code examples
flutterdartmobileflutter-provider

Flutter ExpansionPanelList - expansionCallback not working


I am implementing the ExpansionPanelList by using the Provider package instead of making the Widget be statefulWidget. However, the expansionCallback never gets working. If I click on the first collapsed list on the screen, it will never get expanded. Specifically, data[0].isExpanded is always false in SummitView.dart. There's no error reported in the console.

Here's my SummitView.dart:

import 'package:delta_y/modules/summits/model/Item.dart';
import 'package:delta_y/modules/summits/model/ItemsList.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class SummitView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Summit/Milestone"),
        ),
        body: ChangeNotifierProvider(
          create: (context) => ItemsList(),
          child: Summit(),
        ));
  }
}

class Summit extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final itemModel = Provider.of<ItemsList>(context);
    // List<Item> _data = generateItems(8);
    itemModel.generateItems(8);

    return Stack(children: <Widget>[
      SingleChildScrollView(
        child: Container(
          child: ExpansionPanelList(
            expansionCallback: (int index, bool isExpanded) {
              itemModel.setExpanded(index, isExpanded);
              print("INDEX : $index ... $isExpanded");
            },
            children: itemModel.data.map<ExpansionPanel>((Item item) {
              bool isData0Expanded = itemModel.data[0].isExpanded;
              print('**********');
              print(isData0Expanded);
              print('**********');

              return ExpansionPanel(
                headerBuilder: (BuildContext context, bool isExpanded) {
                  return ListTile(
                    title: Text(item.headerValue),
                  );
                },
                body: ListTile(
                    title: Text(item.expandedValue),
                    subtitle:
                    Text('To delete this panel, tap the trash can icon'),
                    trailing: Icon(Icons.delete),
                    onTap: () {
                      itemModel.deleteItem(item);
                    }),
                isExpanded: item.isExpanded,
              );
            }).toList(),
          ),
        ),
      ),
      Align(
          alignment: Alignment.bottomCenter,
          child: FractionallySizedBox(
            child: ElevatedButton(
              style: ElevatedButton.styleFrom(
                primary: Colors.white,
                onPrimary: Colors.black,
              ),
              onPressed: () {
                Navigator.pop(context);
              },
              child: Text('Create Summit'),
            ),
            widthFactor: 0.9,
          )),
    ]);
  }
}

Here's the ItemsList.dart:

import 'package:flutter/cupertino.dart';
import 'Item.dart';

class ItemsList extends ChangeNotifier{
  List<Item> _data;

  List<Item> get data => _data;

  generateItems(int numberOfItems) {
    _data = List.generate(numberOfItems, (int index) {
      return Item(
        headerValue: 'Panel $index',
        expandedValue: 'This is item number $index',
      );
    });
  }

  setExpanded(int index, bool isExpanded) {
    _data[index].isExpanded = !isExpanded;
    notifyListeners();
  }

  deleteItem(Item item) {
    _data.removeWhere((currentItem) => item == currentItem);
    notifyListeners();
  }
}

Here's the Item.dart:

import 'package:flutter/material.dart';

class Item{
  Item({
    @required this.expandedValue,
    @required this.headerValue,
    this.isExpanded = false,
  });

  String expandedValue;
  String headerValue;
  bool isExpanded;
}

Solution

  • The problem is caused by this line:

    class Summit extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
    
        // ... other lines
    
        itemModel.generateItems(8); <---- 
    

    When calling a method within the build(), it will get called everytime the screen receive an event from the Provider. In this case, your list of items keep being generated everytime, which make the isExpanded always false.

    To fix this, it's best to add some conditions to prevent the list of items from resetting, or generate the items when creating the ItemsList, something like:

    body: ChangeNotifierProvider(
        create: (context) => ItemsList()..generateItems(8),
        child: Summit(),
    

    Result:

    enter image description here