Remove dynamically a widget from a dynamically created list flutter

不羁岁月 提交于 2021-02-11 14:46:55

问题


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:

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),
        ),
      ),
    );
  }
}

回答1:


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'),
        ),
      ],
    );
  }
}


来源:https://stackoverflow.com/questions/65003981/remove-dynamically-a-widget-from-a-dynamically-created-list-flutter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!