How to dynamically update a particular list view item while using a ListView.builder?

Viewed 8302

I am making a list view in Flutter. I want to update an item's property when the item is long pressed.

Following is the complete Code:

// main.dart
import 'package:LearnFlutter/MyList.dart';
import 'package:flutter/material.dart';
import 'MyList.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'List Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'My list demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: MyList(),
    );
  }
}

// MyList.dart
import 'package:flutter/material.dart';

class Item {
  Item(String name, bool selected, Color color) {
    _name = name;
    _selected = selected;
    _color = color;
  }

  String _name;
  bool _selected;
  Color _color;

  String getName() {
    return _name;
  }

  bool isSelected() {
    return _selected;
  }

  void toggleSelected() {
    _selected = !_selected;
  }

  void setColor(Color color) {
    _color = color;
  }

  Color getColor() {
    return _color;
  }
}

class MyList extends StatefulWidget {
  @override
  _CardyState createState() => new _CardyState();
}

class _CardyState extends State<MyList> {
  @override
  Widget build(BuildContext context) {
    var itemsList = [
      Item('My item1', false, Colors.grey[200]),
      Item('My item2', false, Colors.grey[200]),
      Item('My item3', false, Colors.grey[200]),
    ];

    return ListView.builder(
      itemCount: itemsList.length,
      itemBuilder: (context, index) {
        return Card(
          child: ListTile(
            leading: Icon(Icons.train),
            title: Text(itemsList[index].getName()),
            trailing: Icon(Icons.keyboard_arrow_right),
            tileColor: itemsList[index].getColor(),
            selected: itemsList[index].isSelected(),
            onLongPress: () {
              toggleSelection(itemsList[index]);
            },
          ),
        );
      },
    );
  }

  void toggleSelection(Item item) {
    print(item.getName() + ' long pressed');
    setState(() {
      item.toggleSelected();
      if (item.isSelected()) {
        item.setColor(Colors.blue[200]);
      } else {
        item.setColor(Colors.grey[200]);
      }
    });
  }
}

Question:
In the above code toggleSelection is getting called on long press event. But the item's color does not get updated. What am I doing wrong?

2 Answers

The main reason it is not functioning properly is that you have no state in your class Item, so you are not re-building/updating anything. If you would like to handle it there in the class, then you will need to extend it to the ChangeNotifier. You will also need to use the ChangeNotifierProvider, look at the docs for help: https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple

You will need the provider package: https://pub.dev/packages/provider

Class Item

class Item extends ChangeNotifier {
  Item(String name, Color color) {
    _name = name;
    _color = color;
  }

  int selectedIndex; // to know active index
  String _name;
  Color _color;

  String getName() {
    return _name;
  }

  void toggleSelected(int index) {
    selectedIndex = index;

    notifyListeners(); // To rebuild the Widget
  }

  void setColor(Color color) {
    _color = color;

    notifyListeners();
  }

  Color getColor() {
    return _color;
  }
}

Widget List

class MyList extends StatefulWidget {
  @override
  _CardyState createState() => new _CardyState();
}

class _CardyState extends State<MyList> {
  @override
  Widget build(BuildContext context) {
    final items = Provider.of<Item>(context); // Accessing the provider

    bool selected = false; // default val. of bool

    var itemsList = [
      Item('My item1', Colors.grey[200]),
      Item('My item2', Colors.grey[200]),
      Item('My item3', Colors.grey[200]),
    ];

    return ListView.builder(
      itemCount: itemsList.length,
      itemBuilder: (context, index) {
        return Card(
          child: ListTile(
            leading: Icon(Icons.train),
            title: Text(itemsList[index].getName()),
            trailing: Icon(Icons.keyboard_arrow_right),
            tileColor: items.selectedIndex == index
                ? items.getColor()
                : Colors.grey[200],
            selected: items.selectedIndex == index ? true : false,
            onLongPress: () {
              setState(() => selected = !selected);

              items.toggleSelected(index);

              if (selected) {
                items.setColor(Colors.red);
              }
            },
          ),
        );
      },
    );
  }
}

make MyList into a stateless widget keep all the data that it should show in the HomePage which is a statefull widget including the data about the selected items. then pass the data into MyList

here is how your MyList could be

class MyList extends StatelessWidget {
  final List<Item> items;
  final List<int> selectedItemIdList;
  final void Function(Item) onLongClick;

  MyList(this.items, this.selectedItemIdList, this.onLongClick);

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context, position) {
        //remember all you need to do here is to create your item based on the data you have
        var item = items[position];
        var isSelected = items.firstWhere((element) => item.id == element.id) != null;
        if (isSelected) {
          //build and return a widget with selected look
        } else {
          return GestureDetector(
            onLongPress: () => onLongClick(item), //changes data in homepage then MyList will be updated automatically
            child: Container(
              //rest of your widget
            ),
          );
        }
      },
      itemCount: items.length,
    );
  }
}

inside your HomePageState


//all the data the list it build from should be stored here not inside the list. and 

List<Item> items = [ ... ];
List<int> selectedItemIdList = [ ... ];

//MyList is just a Stateless widget that only shows this data change (a very DUMB view as one could say)

  @override
  Widget build(BuildContext context) {
        return MyList(items, selectedItemIdList, (item) { 
            setState((){
               selectedItemIdList.add(item.id);
            });
        });
  }
Related