Flutter - Multiselection ListView

Viewed 10292

In my app, I do have a list, on which I have implemented the long press selection of this post of Raouf Rahiche. When the selection is enabled I do have a different appbar, that has an IconButton on it, that should disable the selection. But I do not know how to do that.

Till now it is not working the way it should. The behaviour is displayed in the video below. enter image description here

The longpress-selection is a StatefulWidget:

class _SelectableItems extends State<SelectableItems> {
  bool isSelected = false;
  GoogleMaterialColors googleMaterialColors = new GoogleMaterialColors();

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

  @override
  Widget build(BuildContext context) {
    return new GestureDetector(
        onLongPress: () {
          setState(() {
            isSelected = !isSelected;
          });
          widget.callback();
        },
        onTap: () {
          setState(() {
            isSelected = !isSelected;
          });
          if (widget.longPressEnabled) {
            widget.callback();
          } else {
            Navigator.push(
              context, 
              MaterialPageRoute(builder: (context)=>RecipeDetails(widget.name))
            );
          }
        },
        child: ListTile(
          leading: CircleAvatar(
            child: (isSelected
              ? Icon(
                Icons.check,
                color: Colors.white,
              )
              : (widget.image != "no image"
                ? Container(
                  width: 40.0,
                  height: 40.0,
                  decoration: new BoxDecoration(
                    image: new DecorationImage(
                      colorFilter: ColorFilter.mode(Colors.black.withOpacity(0.2), BlendMode.darken),
                      image: AssetImage(widget.image),
                      fit: BoxFit.cover,
                    ),
                    borderRadius: new BorderRadius.all(new Radius.circular(50.0)),
                  ),
                )
                : Text(
                  widget.name[0].toUpperCase(),
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 21.0,
                    fontWeight: FontWeight.w400
                  ),
                )
              )
            ),
            backgroundColor: (isSelected
              ? googleMaterialColors.primaryColor()
              : widget.color.withOpacity(1.00)
            )
          ),
          title: Padding(
            padding: EdgeInsets.only(top: 25.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                widget.title
              ],
            ),
          ),
        ),
      );
  }
}

I am calling this widget inside a SideHeaderListView like this:

bool longPressFlag = false;
List<String> indexList = new List();
//other code

                return SideHeaderListView(                  
                    hasSameHeader: (int a, int b){
                      return snapshot.data[a].name[0] == snapshot.data[b].name[0];                  
                    },
                    itemCount: snapshot.data.length,
                    headerBuilder: (BuildContext context, int index){
                      return new Padding(
                        padding: EdgeInsets.only(top: 30.0, left: 20.0, right: 25.0),
                        child: Container(
                          width: 10.0,
                          child: Text(
                            snapshot.data[index].name[0].toUpperCase(),
                            style: TextStyle(
                              color: googleMaterialColors.primaryColor().withGreen(120),                        
                              fontFamily: "Google-Sans",
                              fontSize: 15.0,
                              fontWeight: FontWeight.w600
                            ),
                          ),
                        ),
                      );
                    },
                    itemExtend: 70.0,
                    itemBuilder: (BuildContext context, int index){

                      Color usedColor = convertColor.convertToColor(snapshot.data[index].backgroundColor);                    
                      String image = snapshot.data[index].image;


                      return SelectableItems(
                        color: usedColor,
                        name: snapshot.data[index].name,
                        title: (searchController.text.isEmpty
                          ? Text(snapshot.data[index].name)
                          : recipeName(searchCondition, snapshot.data[index].name)
                        ),
                        index: index,
                        image: image,
                        longPressEnabled: longPressFlag,
                        //isSelected: selectedFlag,
                        callback: () {
                          if (indexList.contains(snapshot.data[index].name)) {
                            indexList.remove(snapshot.data[index].name);
                          } else {
                            indexList.add(snapshot.data[index].name);
                          }
                          longPress();
                        },
                      );
                    },
                  );

void longPress() {
    setState(() {
      if (indexList.length == 0) {
        longPressFlag = false;
      } else {
        longPressFlag = true;
      }
    });
  }

I hope somebody would be able to solve my problem. Thanks in advance.

2 Answers

The first thing is that you should add each item a key in constructor like this :

MyItem({Key key}): super(key: key);

Why a key ? A key allow you to identify your widget correctly. See in doc :

A new widget will only be used to update an existing element if its key is the same as the key of the current widget associated with the element.

Create a GlobalKey (a GLobal key extends Key) For each item to access the widget from, create a global key. From the doc :

A key that is unique across the entire app. Global keys uniquely identify elements. Global keys provide access to other objects that are associated with elements, such as the a [BuildContext] and, for [StatefulWidget]s, a [State].

Add in the code the creation of a global key for each item (in your SelectableItem for you) :

...
var key = new GlobalKey<SelectableItem >();
this.items.put(position, key);
return new SelectableItem(key: key,...);

Items is a map where you can save position and Global Key. Now when you want to select a View from the parent just access the globalKey from the map of items and access the widget to do what you want.(update, uncheck, etc...)

Edit : exemple :

class SideHeaderListView  {

  Map<int, GlobalKey<_SelectableItems>> map = new Map();

  create() {
    for (int i = 0; i< 10; i++) {
      var key = new GlobalKey<_SelectableItems>();
      var item = new SelectableItems(key: key);
      map.putIfAbsent(i, () => key);
    }
  }

  redrawItem(int i) {
    var widget = this.map[i].currentState;
    widget.redraw();
  }
}

class SelectableItems extends StatefulWidget {

  SelectableItems({key: Key}) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return new _SelectableItems();
  }
}

class _SelectableItems extends State<SelectableItems> {

  @override
  Widget build(BuildContext context) {
    return new Text("test");
  }

  redraw() {
    setState(() {

    });
  }

 }

You have commented part of code - //isSelected: selectedFlag, I think, you have to add this field to your widget

class SelectableItems extands StatefulWidget {
    SelectableItems({this.isSelected = false});
    final bool isSelected;
    ...

class _SelectableItems extends State<SelectableItems> {
  bool isSelected;

  @override
  void initState() {
    isSelected = widget.isSelected ?? false;
    super.initState();
  }
  ....

And when you're creating list of items:

return SelectableItems(
  ...
  isSelected: indexList.contains(snapshot.data[index].name)

I think this could work

Related