Search code examples
listviewfluttersetstate

Flutter Setstate inside Listview.builder's builditem function not reloading


I have a streambuilder that loads information from firestore. Then I use a listview.builder to display the information. The displayed information is being called by the function firstBuildItem or buildItem.

The functionality that I want is that when the user clicks on the container that is wrapped with a gesturedetector, I want another widget to be expanded below the main widget. I'm not sure how to achieve this with flutter because since each message is an item insider listview.builder, the boolean value needs to be local inside the function since every function has its own boolean that is checking for if the list item was clicked. I would like to find out how to fix my code so that each item responds when clicked inside the listview by expanding.

ListView.builder(
padding: EdgeInsets.all(10.0),
itemBuilder: (context, index) =>
index == 0 ? firstBuildItem(index, snapshot.data.documents[index]) : buildItem(index, snapshot.data.documents[index], snapshot.data.documents[index-1]),
itemCount: snapshot.data.documents.length,
reverse: true,
controller: listScrollController,
),

then here is the builditem function:

Widget firstBuildItem(int index, DocumentSnapshot mainDocument) {

  ///If the box is expanded
  bool _isExpanded = false;

  ///Toggle the box to expand or collapse
  void _toggleExpand() {
    setState(() {
      _isExpanded = !_isExpanded;
    });
  }


  return GestureDetector(

    onTap: _toggleExpand,

    child: Container(

      color: _isExpanded ? Colors.blueGrey : null,

      child: Column(


        children: <Widget>[

          Column(
            children: <Widget>[

              SizedBox(height: 20.0),

              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[

                  ChatDateHeader(timestamp: mainDocument['timestamp']),

                ],
              ),
            ],
          ),

          Column(

            children: <Widget>[

              Row(
                children: <Widget>[
                  Container(
                    child: Text(
                      mainDocument['content'],
                      style: TextStyle(color: primaryColor),
                    ),
                    padding: EdgeInsets.fromLTRB(15.0, 10.0, 15.0, 10.0),

                  ),
                ],
                mainAxisAlignment: MainAxisAlignment.end,
              ),

              getTime(mainDocument['timestamp'], true),

              ExpandedSection(
                expand: _isExpanded,
                child: getDate(mainDocument['timestamp'], true),
              ),

              SizedBox(height: 20.0),

            ],
            crossAxisAlignment: CrossAxisAlignment.end,
          ),
        ],
      ),
    ),
  );
}

and then the expanded widget:

class ExpandedSection extends StatefulWidget {

  final Widget child;
  final bool expand;
  ExpandedSection({this.expand = false, this.child});

  @override
  _ExpandedSectionState createState() => _ExpandedSectionState();
}

class _ExpandedSectionState extends State<ExpandedSection> with SingleTickerProviderStateMixin {
  AnimationController expandController;
  Animation<double> animation;

  @override
  void initState() {
    super.initState();
    prepareAnimations();
  }

  ///Setting up the animation
  void prepareAnimations() {
    expandController = AnimationController(
        vsync: this,
        duration: Duration(milliseconds: 500)
    );
    Animation curve = CurvedAnimation(
      parent: expandController,
      curve: Curves.fastOutSlowIn,
    );
    animation = Tween(begin: 0.0, end: 1.0).animate(curve)
      ..addListener(() {
        setState(() {

        });
      }
      );
  }

  @override
  void didUpdateWidget(ExpandedSection oldWidget) {
    super.didUpdateWidget(oldWidget);
    if(widget.expand) {
      expandController.forward();
    }
    else {
      expandController.reverse();
    }
  }

  @override
  void dispose() {
    expandController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SizeTransition(
        axisAlignment: 1.0,
        sizeFactor: animation,
        child: widget.child
    );
  }
}

Solution

  • I solved the issue by turning firstBuildItem widget function into a stateful widget of it's own class. And then passing a key to the function. With this, setState worked without rebuilding the entire streambuilder.

    ListView.builder(
                      padding:    EdgeInsets.all(10.0),
                      itemCount:  snapshot.data.documents.length,
                      reverse:    true,
                      controller: listScrollController,
                      itemBuilder: (context, index) =>
    
                        index == snapLength ?
    
                        FirstChatBuildMessageItem(
                          key:          Key('counter-${index}'),
                          id:           id,
                          peerAvatar:   peerAvatar,
                          index:        index,
                          mainDocument: snapshot.data.documents[index],
                          listMessage: listMessage,
                        )
                            :
    
                        //index == 0 ?
    
                        ChatBuildMessageItem(
                          key:              Key('counter-${index}'),
                          id:               id,
                          peerAvatar:       peerAvatar,
                          index:            index,
                          mainDocument:     snapshot.data.documents[index],
                          previousDocument: snapshot.data.documents[index + 1],
                          listMessage:      listMessage,
                        ),
    
                    ),