Home > database >  Flutter change state of an item in a ListView
Flutter change state of an item in a ListView

Time:11-09

I'm trying to use a ListTile widget inside a ListView that will change depending on some information received elsewhere in the app. In order to practice, I tried to make a little Dartpad example.

The problem is as follows: I have been able to change the booleans and data behind the items, but they don't update within the ListView.builder.

I have ListTile widgets inside the ListView that I want to have four different states as follows: idle, wait, requested, and speaking.

The different states look like this, as an example: enter image description here

I am trying to change the individual state of one of these items, but I haven't been able to get them to properly update within the ListView.

The ListTile code looks like this. Most of this code is responsible for just handling what the UI should look like for each state:

class UserItem extends StatefulWidget {
  String name;
  UserTileState userTileState;
  UserItem(this.name, this.userTileState);

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

class UserItemState extends State<UserItem> {
  String _getCorrectTextState(UserTileState userTileState) {
    switch (userTileState) {
      case UserTileState.speaking:
        return "Currently Speaking";
      case UserTileState.requested:
        return "Speak Request";
      case UserTileState.wait:
        return "Wait - Someone Speaking";
      case UserTileState.idle:
        return "Idle";
    }
  }

  Widget _getCorrectTrailingWidget(UserTileState userTileState) {
    switch (userTileState) {
      case UserTileState.speaking:
        return const CircleAvatar(
            backgroundColor: Colors.green,
            foregroundColor: Colors.white,
            child: Icon(Icons.volume_up));
      case UserTileState.requested:
        return CircleAvatar(
          backgroundColor: Colors.green.shade100,
          foregroundColor: Colors.grey[700],
          child: const Icon(Icons.volume_up),
        );
      case UserTileState.wait:
        return CircleAvatar(
          backgroundColor: Colors.green.shade100,
          foregroundColor: Colors.grey[700],
          child: const Icon(Icons.volume_off),
        );
      case UserTileState.idle:
        return CircleAvatar(
          backgroundColor: Colors.green.shade100,
          foregroundColor: Colors.grey[700],
          child: const Icon(Icons.volume_off),
        );
    }
  }

  void kickUser(String name) {
    print("Kick $name");
  }

  @override
  Widget build(BuildContext context) {
    return ListTile(
        onLongPress: () {
          kickUser(widget.name);
        },
        title: Text(widget.name,
            style: TextStyle(
                fontWeight: widget.userTileState == UserTileState.speaking ||
                        widget.userTileState == UserTileState.requested
                    ? FontWeight.bold
                    : FontWeight.normal)),
        subtitle: Text(_getCorrectTextState(widget.userTileState),
            style: const TextStyle(fontStyle: FontStyle.italic)),
        trailing: _getCorrectTrailingWidget(widget.userTileState));
  }
}

enum UserTileState { speaking, requested, idle, wait }

To try and trigger a change in one of these UserTile items, I wrote a function as follows. This one should make an idle tile become a requested tile.

void userRequest(String name) {
    // send a speak request to a tile
    int index = users.indexWhere((element) => element.name == name);
    users[index].userTileState = UserTileState.requested;
  }

I will then run that userRequest inside my main build function inside a button, as follows:

class MyApp extends StatefulWidget {
  @override
  MyAppState createState() => MyAppState();
}

class MyAppState extends State<MyApp> {
  
  void userRequest(String name) {
    // send a speak request to a tile
    int index = users.indexWhere((element) => element.name == name);
    users[index].userTileState = UserTileState.requested;
  }

  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Column(children: [
      Expanded(
          flex: 8,
          child: ListView.separated(
              separatorBuilder: (context, index) {
                return const Divider();
              },
              itemCount: users.length,
              itemBuilder: (context, index) {
                return users[index];
              })),
      Expanded(
          flex: 2,
          child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                
                TextButton(
                    onPressed: () {
                      setState(() {
                        userRequest("Test1");
                      });
                    },
                    child: const Text("Send Request")),
                
              ]))
    ]));
  }
}

When I tap the button to set the value within the first UserTile, nothing happens.

I don't know where to put setState, and the state of the object within the ListView isn't being updated. What is the most simple solution to this problem? Provider? I've used Provider in this same situation and can't get it to work. Is there another simpler solution to this?

How can I change update the state of a specific element within a ListView?

CodePudding user response:

I believe the problem lies here in _getCorrectTextState()

Create a variable, String _status; And do something like this;

     void _getCorrectTextState(UserTileState userTileState) {
            String tmpState;
            switch (userTileState) {
              case UserTileState.speaking:
                tmpState = "Currently Speaking";
              case UserTileState.requested:
                tmpState = "Speak Request";
              case UserTileState.wait:
                tmpState = "Wait - Someone Speaking";
              case UserTileState.idle:
                tmpState = "Idle";
            }
           setState(() => {_state = tmpState});
}

then in your build:

subtitle: _state;

CodePudding user response:

You can use StatefulBuilder() - by using this you will get a separate state for each item in your list, that can be updated

like this:

ListView.separated(
          separatorBuilder: (context, index) {
            return const Divider();
          },
          itemCount: users.length,
          itemBuilder: (context, index) {
            return StatefulBuilder(
              builder: ((context, setStateItem){

         //return_your_listTile_widget_here
         //use setStateItem (as setState) this will update the state of selected item

                setStateItem(() {
                        //code for update the listView item                 
                       });

                  })
                );
          })),
  • Related