M.Ali
M.Ali

Reputation: 10235

Flutter: Maintant state of child stateful widget

I have a listview.builder inside a stateful widget and i made a separate stateful widget for the item (ImageCard). inside the ImageCard widget i have a like button when i click it its color change to red(like), gray(dislike). the problem is that when i scroll down and return back the color is always gray which means that no state is saved! how can i notify the parent stateful widget to keep the state?

Parent stateful widget

@override
  Widget build(BuildContext context) {
    return _buildListView(models, _scrollController);
  }

  Widget _buildListView(
      List<PhotoModel> models, ScrollController scrollController) {
    return Container(
        child: ListView.builder(
            controller: scrollController,
            itemCount: models.length,
            itemBuilder: (context, int index) {
              if (index == models.length - 1) {
                return SpinKitThreeBounce(
                  color: Colors.purple,
                  size: 30.0,
                );
              } else {
                return ImageCard(
                    models[index].regularPhotoUrl,
                    models[index].mediumProfilePhotoUrl,
                    models[index].name,
                    models[index].color);
              }
            }));
  }

child stateful widget

class ImageCard extends StatefulWidget {
  final String imageUrl, userProfilePic, userName, color;

  ImageCard(this.imageUrl, this.userProfilePic, this.userName, this.color);

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

class _ImageCardState extends State<ImageCard> {
  bool isLiked = false, isFollowing = false;

  @override
  Widget build(BuildContext context) {
    return new Card( ....

void _onLikedBtnClicked() {
    setState(() {
      if (isLiked)
        isLiked = false;
      else {
        isLiked = true;
      }
    });
  }

Upvotes: 3

Views: 4502

Answers (3)

First_Strike
First_Strike

Reputation: 1089

Flutter will automatically disposes the widget that moves out of screen, and when they re-appear, they will be re-built rather than recovered.

So common practice is to save the state in a high-level widget, which contains at least a complete aspect of business logic and is not going to be disposed anytime soon. Then a change in the state is mapped into child widgets.

For your specific case, a simple solution is: you store the information in the parent widget, and maps them to a ImageCard inside the parent widget's build function.

Add isliked,isfollowing property to the model, then

class SomeParentState extends State<SomeParent> {
  List<Model> models;

  //.......

  @override
  Widget build(BuildContext context) {
    return _buildListView(models, _scrollController);
  }

  Widget _buildListView(List<PhotoModel> models,
      ScrollController scrollController) {
    return Container(
        child: ListView.builder(
            controller: scrollController,
            itemCount: models.length,
            itemBuilder: (context, int index) {
              if (index == models.length - 1) {
                return SpinKitThreeBounce(
                  color: Colors.purple,
                  size: 30.0,
                );
              } else {
                return ImageCard(
                  models[index].regularPhotoUrl,
                  models[index].mediumProfilePhotoUrl,
                  models[index].name,
                  models[index].color,
                  models[index].isLiked,
                  models[index].isFollowing,
                      () {
                    setState(() {
                      models[index].isLiked = !models[index].isLiked;
                    });
                  },
                      () {
                    setState(() {
                      models[index].isFollowing = !models[index].isFollowing;
                    });
                  },
                );
              }
            }));
  }
}


class ImageCard extends StatelessWidget{

  ImageCard(
      //...,
      this.isLiked,
      this.isFollowing,
      this.likeBtnClickedListener,
      this.followBtnClickedListener,
      )
  //...
  Widget build(BuildContext context){
    return Card(
      //.......
      IconButton(
        onPressed: likeBtnClickedListener,
      ),
      IconButton(
        onPressed: followBtnClickedListener,
      ),
    )
  }
}

This should basically solve your problem. Anyway, it is easier to access and sync the data in the child widgets in this method.

If you find it easier to just keep the child widget alive, you can read the documentation of AutomaticKeepAliveClientMixin. It will stop flutter from killing this widget when it moves out of sight. But it is risky of causing memory leak.

Upvotes: 5

R&#233;mi Rousselet
R&#233;mi Rousselet

Reputation: 276891

To maintain the state of a widget inside a ListView, you need to AutomaticKeepAlive or AutomaticKeepAliveMixin (for custom widgets)

This will ensure the State instance is not destroyed when leaving the screen

ListView(
  children: [
    // Not kept alive
    Text('Hello World'),
    // kept alive
    AutomaticKeepAlive(
     child: Text("Hello World"),
    ),
  ]
),

Upvotes: 1

leodriesch
leodriesch

Reputation: 5780

You should keep your state separately then. You could make a List<bool> and have one value in there for each of the List items. You probably want to save or use the data at some point anyways, then this mechanism is going to be useless.

Upvotes: -3

Related Questions