2

I followed this example to remove a desired element from a list, and it worked fine. I then made some changes as the size of my list is not predefined (aka 5 in the example), but changes through a button. After adapting the code to my needs, I observed two problems:

  1. The elements hash codes changes everytime I hit + to add a new element. Is this correct as it's redrawing the list, or is incorrect?
  2. When deleting an element, besides changing the hash codes of all the other elements, only the last one is deleted, not the selected one.

Here is the execution:

enter image description here

Here is the code:

import 'package:flutter/material.dart';

class StreamBuilderIssue extends StatefulWidget {
  @override
  _StreamBuilderIssueState createState() => _StreamBuilderIssueState();
}

class _StreamBuilderIssueState extends State<StreamBuilderIssue> {
  List<ServiceCard> serviceCardList;
  int numElements = 1; //dynamic number of elements

  void removeServiceCard(index) {
    setState(() {
      serviceCardList.remove(index);
      numElements--;
    });
  }


  @override
  Widget build(BuildContext context) {
    serviceCardList = List.generate(numElements, (index) => ServiceCard(removeServiceCard, index: index));
    return Scaffold(
      body: ListView(
        children: <Widget>[
          ...serviceCardList,
          new FlatButton(
            onPressed: (){
              setState(() {
                numElements++;
              });
            },
            child: new Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

class ServiceCard extends StatelessWidget {
  final int index;
  final Function(ServiceCard) removeServiceCard;

  const ServiceCard(this.removeServiceCard, {Key key, @required this.index})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Text(
            'Index: ${index.toString()} Hashcode: ${this.hashCode.toString()}'),
        RaisedButton(
          color: Colors.accents.elementAt(3 * index),
          onPressed: () {
            removeServiceCard(this);
          },
          child: Text('Delete me'),
        ),
      ],
    );
  }
}

How can I fix this problem and correctly delete the selected element?

Can someone explain the constructor of the ServiceCard class? I don't understand the syntax.

EDIT: This same problem adapted to my code and with a Stateful class, rather than Stateless:

import 'package:flutter/material.dart';

class CreateForms extends StatefulWidget{
  @override
  _CreateForms createState() => _CreateForms();

}

class _CreateForms extends State<CreateForms>{
  final _formKey = GlobalKey<FormState>();
  List<Question> _questions = [];
  final myController = TextEditingController();

  @override
  void dispose() {
    // Clean up the controller when the widget is disposed.
    myController.dispose();
    super.dispose();
  }

  void removeQuestion(index) {
    print("Removing " + index.hashCode.toString());
    setState(() {
      _questions.remove(index);
    });
  }

  void addServiceCard() {
    setState(() {
      Question question = Question(removeQuestion, _questions.length);
      _questions.add(question);
      print("Adding " + question.hashCode.toString());
    });
  }

  @override
  void initState() {
    addServiceCard(); //Initialize with 1 item
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    for (var i = 0; i < _questions.length; i++){
      print("Iterating on scaffold over: " + _questions[i].hashCode.toString());
    }
    return Scaffold(
      key: _formKey,
      resizeToAvoidBottomPadding: true,
      backgroundColor: Colors.white,
      body: Container(
        padding: new EdgeInsets.all(20.0),
        width: double.infinity,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            SizedBox(height: 20),
            _displayTitle(),
            new Expanded(
              // height: 300,
              child: new ListView(
                children: <Widget>[
                  ..._questions,
                ],
                scrollDirection: Axis.vertical,
              ),
            ),
            Row(
              children: <Widget>[
                new FlatButton(
                  onPressed: () => addServiceCard(),
                  child: new Icon(Icons.ac_unit),
                ),
              ],
            ),
            const Divider(
              color: Colors.black,
              height: 20,
              thickness: 1,
              indent: 0,
              endIndent: 0,
            ),
            Row(
              children: <Widget>[
                new Expanded(child: FlatButton(
                  onPressed: () {
                    //return to prev window
                    Navigator.pop(context);
                  },
                  padding: EdgeInsets.symmetric(vertical: 10),
                  child: Text('Cancel', style: TextStyle(fontSize: 20.0)),
                ), flex: 2),
                new Expanded(child: RaisedButton(
                  child: Text('Create form', style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold)),
                  onPressed: () {
                    //FormModel form = FormModel(myController.text, _questions);
                    //String json = jsonEncode(form);
                    //print(json);

                    //_formKey.currentState.save();
                    //Navigator.push(context, MaterialPageRoute(builder: (context) => Result(model: this.model)));
                  },
                ), flex: 3)
              ],
            )
          ],
        ),
      ),
    );
  }

  _displayTitle(){
    return TextField(
      controller: myController,
      maxLines: null,
      autofocus: true,
      decoration: InputDecoration(
          border: InputBorder.none,
          hintText: 'Form title'
      ),
      style: TextStyle(
          color: Colors.black,
          fontSize: 20,
          fontWeight: FontWeight.bold
      ),
    );
  }
}

class Question extends StatefulWidget {
  final int index;
  final Function(Question) removeQuestion;

  const Question(this.removeQuestion, this.index);

  void remove(){
    print("Called remove on " + this.hashCode.toString());

    removeQuestion(this);
  }

  @override
  _Question createState() => new _Question(index, remove);
}

class _Question extends State<Question> {
  final int questionIndex;
  final Function() remove;
  _Question(this.questionIndex, this.remove);
  int _numOptions = 1;

  @override
  Widget build(BuildContext context) {
    List<Widget> _options = new List.generate(_numOptions, (int i) => new Options(_numOptions));
    return Container(
      color: Colors.accents.elementAt(3 * questionIndex),
      child: Column(
        children: <Widget>[
          Row(
            children: <Widget>[
              Flexible(
                flex: 2,
                child: new TextFormField(
                  maxLines: null,
                  decoration: InputDecoration(
                    border: InputBorder.none,
                    hintText: 'Question title',
                    isDense: true,
                    prefixIcon:Text((questionIndex + 1).toString() + ". "),
                    prefixIconConstraints: BoxConstraints(minWidth: 0, minHeight: 0),
                  ),
                ),
              ),
              new IconButton(
                icon: new Icon(Icons.delete),
                onPressed: (){
                  print("delete pressed");
                  remove();
                }
              ),
            ],
          ),
          new ListView(
            physics: NeverScrollableScrollPhysics(),
            children: _options,
            scrollDirection: Axis.vertical,
            shrinkWrap: true,
          ),
          Row(
            children: <Widget>[
              new FlatButton(
                onPressed: (){
                  setState(() {
                    _numOptions++;
                  });
                },
                child: new Icon(Icons.add),
              ),
              new FlatButton(
                onPressed: (){
                  setState(() {
                    _numOptions--;
                  });
                },
                child: new Icon(Icons.remove),
              ),
            ],
          )
        ],
      ),
    );
  }
}


class Options extends StatefulWidget {
  int _numOptions;

  Options(int numOptions){
    this._numOptions = numOptions;
  }

  @override
  State<StatefulWidget> createState() => new _Options(_numOptions);
}

class _Options extends State<Options> {
  int _numOptions;
  _Options(int numOptions){
    this._numOptions = numOptions;
  }

  @override
  Widget build(BuildContext context) {
    return Padding(padding: EdgeInsets.only(left: 15),
      child: new TextFormField(
        maxLines: null,
        decoration: InputDecoration(
          border: InputBorder.none,
          hintText: 'Option',
          isDense: true,
          prefixIcon:Text(_numOptions.toString() + ". "),
          prefixIconConstraints: BoxConstraints(minWidth: 0, minHeight: 0),
        ),
      ),
    );
  }
}
0

1 Answer 1

6

It changes the hash 'cause you are defining the list in the build method, basically everytime the widget rebuilds itself that method is called (So everytime you call setState() ), and everytime you re-define the list it randomizes the new hashcode.

I changed a bit your code, this should solve your problems and it should look a bit cleaner:

class StreamBuilderIssue extends StatefulWidget {
  @override
  _StreamBuilderIssueState createState() => _StreamBuilderIssueState();
}

class _StreamBuilderIssueState extends State<StreamBuilderIssue> {
  
  List<ServiceCard> serviceCardList = []; //Define the dynamic list

  void removeServiceCard(index) {
    setState(() {
      serviceCardList.remove(index);
    });
  }

  void addServiceCard() {
    setState(() {
    serviceCardList.add(ServiceCard(removeServiceCard, index: serviceCardList.length));
    });
  }


  @override
  void initState() {
    addServiceCard(); //Initialize with 1 item
    super.initState();
  }



  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView(
        children: <Widget>[
          ...serviceCardList,
          FlatButton(
            onPressed: () => addServiceCard(),
            child: new Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

class ServiceCard extends StatelessWidget {
  final int index;
  final Function(ServiceCard) removeServiceCard;

  const ServiceCard(this.removeServiceCard, {Key key, @required this.index})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Text(
            'Index: ${index.toString()} Hashcode: ${this.hashCode.toString()}'),
        RaisedButton(
          color: Colors.accents.elementAt(3 * index),
          onPressed: () {
            removeServiceCard(this);
          },
          child: Text('Delete me'),
        ),
      ],
    );
  }
}
Sign up to request clarification or add additional context in comments.

9 Comments

Perfect, works fine. Just edit the post so that it has serviceCardList.length instead of serviceCardList.length() for future users looking at this. Thank you :)
I'm playing with your example. What if ServiceCard extended StatefulWidget? Then all relevant code would belong to a subclass called _ServiceCard that extends State<ServiceCard>. How would you achieve the removeServiceCard() then?
In the same way, stateful widgets are meant to modify the state of the widget itself, not its presence in a parent / list. So, you still need to pass the remove callback, otherwise it won't work, if you want to modify a text, a color or whatever property you want into that specific card then a statefull widget is the way
I did pass the remove callback, but I can't manage to make it work. Passed the callback to _ServiceCard and used removeQuestion(widget); .Then tried with removeQuestion(parentClass); (where parentClass is a this in the _ServiceCard constructor), and also tried making a ´remove()´ function, that contains the callback function removeServiceCard() in ServiceCard, called from the _ServiceCard class. None of the three worked. Oddly enough, I print the hashes and they are ok. They don't change and the one meant to be deleted, is deleted from the list, but not in the view.
Done. Current version is the one with a remove() function in the Stateful class.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.