flutter, The index value I asked to remove is different from the index value that was actually deleted at List<Widget>

Viewed 353
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: Scaffold(body: Center(child: TestWidget())));
  }
}

class MovableItem extends StatefulWidget {
  final int removeIndex;
  final double xInit;
  final double yInit;

  MovableItem({
    @required this.removeIndex,
    @required this.xInit,
    @required this.yInit,
  });

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

class _MovableItemState extends State<MovableItem> {
  double xPosition = 0;
  double yPosition = 0;

  double _height = 150;
  double _width = 150;

  @override
  void initState() {
    super.initState();
    xPosition = widget.xInit;
    yPosition = widget.yInit;

    print(widget.removeIndex);
  }

  @override
  Widget build(BuildContext context) {

    return Positioned(
      top: yPosition,
      left: xPosition,
      child: GestureDetector(
        onPanUpdate: (DragUpdateDetails tapInfo) {
          setState(() {
            xPosition += tapInfo.delta.dx;
            yPosition += tapInfo.delta.dy;
          });
        },
        child: Row(
          children: <Widget>[
            GestureDetector(
                child: Container(height: _height, width: _width, child: Text(widget.removeIndex.toString()))
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();

    print(widget.removeIndex);
  }
}


class TestWidget extends StatefulWidget {
  TestWidgetState createState() => TestWidgetState();
}

class TestWidgetState extends State<TestWidget> {

  List<Widget> _stack = [];

  @override
  void initState() {
    super.initState();
    _stack.add(MovableItem(removeIndex: 0, xInit: 0, yInit:  0,));
    _stack.add(MovableItem(removeIndex: 1, xInit: 20, yInit: 20,));
  }

  void onChangedFunction(int removeIndex) {
    setState(() {
      _stack.removeAt(removeIndex);
    });
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
        child: Column(
          children: <Widget>[
            RaisedButton(
              child: Text("remove 0 index"),
              onPressed: () {
              setState(() {
                _stack.removeAt(0);
              });

            },),
            SizedBox(
              width: 500,
              height: 500,
              child: Stack(
                children: _stack,
              ),
            ),
          ],
        ));
  }
}

When onPressed in RaisedButton, I hope removed zero index to _stack. but actually first index is deleted.

when onPressed is called, I get the following printing result.

1

I expected to print zero. how can I remove zero index in _stack variable?

in another code, first index is removed on _stack variable in this code.

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: Scaffold(body: Center(child: TestWidget())));
  }
}

class TestWidget extends StatefulWidget {
  TestWidgetState createState() => TestWidgetState();
}

class TestWidgetState extends State<TestWidget> {

  List<Widget> _stack = [];

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

    _stack.add(Positioned(
      top: 0,
      left: 0,
      child: Text("0"),
    ));

    _stack.add(Positioned(
      top: 20,
      left: 20,
      child: Text("1"),
    ));
  }
  @override
  Widget build(BuildContext context) {
    return SafeArea(
        child: Column(
          children: <Widget>[
            RaisedButton(
              child: Text("remove 0 index"),
              onPressed: () {
              setState(() {
                _stack.removeAt(0);
              });

            },),
            SizedBox(
              width: 500,
              height: 500,
              child: Stack(
                children: _stack,
              ),
            ),
          ],
        ));
  }
}
1 Answers

you need to add a key value to your widgets

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: Scaffold(body: Center(child: TestWidget())));
  }
}

class MovableItem extends StatefulWidget {
  final int removeIndex;
  final double xInit;
  final double yInit;

  MovableItem({
    Key key,
    @required this.removeIndex,
    @required this.xInit,
    @required this.yInit,
  }) : super(key: key);

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

class _MovableItemState extends State<MovableItem> {
  double xPosition = 0;
  double yPosition = 0;

  double _height = 150;
  double _width = 150;

  @override
  void initState() {
    super.initState();
    xPosition = widget.xInit;
    yPosition = widget.yInit;

    print('created: ${widget.removeIndex} with key ${widget.key}');
  }

  @override
  Widget build(BuildContext context) {

    return Positioned(
      top: yPosition,
      left: xPosition,
      child: GestureDetector(
        onPanUpdate: (DragUpdateDetails tapInfo) {
          setState(() {
            xPosition += tapInfo.delta.dx;
            yPosition += tapInfo.delta.dy;
          });
        },
        child: Row(
          children: <Widget>[
            GestureDetector(
                child: Container(height: _height, width: _width, child: Text(widget.removeIndex.toString()))
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    print('disposed: ${widget.removeIndex} with key ${widget.key}');
  }
}


class TestWidget extends StatefulWidget {
  TestWidgetState createState() => TestWidgetState();
}

class TestWidgetState extends State<TestWidget> {

  List<Widget> _stack = [];

  @override
  void initState() {
    super.initState();
    _stack.add(MovableItem(key: Key('0'), removeIndex: 0, xInit: 0, yInit:  0,));
    _stack.add(MovableItem(key: Key('1'), removeIndex: 1, xInit: 20, yInit: 20,));
  }

  void onChangedFunction(int removeIndex) {
    setState(() {
      _stack.removeAt(removeIndex);
    });
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
        child: Column(
          children: <Widget>[
            RaisedButton(
              child: Text("remove 0 index"),
              onPressed: () {
              setState(() {
                _stack.removeAt(0);
              });

            },),
            SizedBox(
              width: 500,
              height: 500,
              child: Stack(
                children: _stack,
              ),
            ),
          ],
        ));
  }
}

the console output will be

created: 0 with key [<0>]
created: 1 with key [<1>]
disposed: 0 with key [<0>]

Why does this happen? When you setState and delete the wiget at index 0 the build method rebuilds itself and _stack is not the same object anymore, it check that the list now has a different length and delete the last objects (the ones outside of the new length). If this is undesirable you should add a key to your widgets so when they rebuild the state knows which one to delete based on the key value.

You can check also this code where I change the print values so you see what happens when you setState and delete the indexAt(0). didUpdateWidget changes and print the differences between the oldwiget and the new one be rebuilt

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: Scaffold(body: Center(child: TestWidget())));
  }
}

class MovableItem extends StatefulWidget {
  final int removeIndex;
  final double xInit;
  final double yInit;

  MovableItem({
    @required this.removeIndex,
    @required this.xInit,
    @required this.yInit,
  });

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

class _MovableItemState extends State<MovableItem> {
  double xPosition = 0;
  double yPosition = 0;

  double _height = 150;
  double _width = 150;

  @override
  void initState() {
    super.initState();
    xPosition = widget.xInit;
    yPosition = widget.yInit;

    print('created: ${widget.removeIndex}');
  }

  @override
  void didUpdateWidget(MovableItem oldWidget){
    print('updated: oldWidget ${oldWidget.removeIndex} vs newWidget ${widget.removeIndex}');
    super.didUpdateWidget(oldWidget);
  }

  @override
  Widget build(BuildContext context) {

    return Positioned(
      top: yPosition,
      left: xPosition,
      child: GestureDetector(
        onPanUpdate: (DragUpdateDetails tapInfo) {
          setState(() {
            xPosition += tapInfo.delta.dx;
            yPosition += tapInfo.delta.dy;
          });
        },
        child: Row(
          children: <Widget>[
            GestureDetector(
                child: Container(height: _height, width: _width, child: Text(widget.removeIndex.toString()))
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();

    print('disposed: ${widget.removeIndex}');
  }
}


class TestWidget extends StatefulWidget {
  TestWidgetState createState() => TestWidgetState();
}

class TestWidgetState extends State<TestWidget> {

  List<Widget> _stack = [];

  @override
  void initState() {
    super.initState();
    _stack.add(MovableItem(removeIndex: 0, xInit: 0, yInit:  0,));
    _stack.add(MovableItem(removeIndex: 1, xInit: 20, yInit: 20,));
  }

  void onChangedFunction(int removeIndex) {
    setState(() {
      _stack.removeAt(removeIndex);
    });
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
        child: Column(
          children: <Widget>[
            RaisedButton(
              child: Text("remove 0 index"),
              onPressed: () {
              setState(() {
                _stack.removeAt(0);
              });

            },),
            SizedBox(
              width: 500,
              height: 500,
              child: Stack(
                children: _stack,
              ),
            ),
          ],
        ));
  }
}

The console output

created: 0
created: 1
updated: oldWidget 0 vs newWidget 1
disposed: 1

Without a key the 2 objects are created 0 and 1. Then I deleted the one at index 0 and rebuilt the parent, the children update and now (without a key) the widget at index 0 is the only widget and its value updates from 0 to 1 and the one at index 1 doesn't exist anymore (because your list now has a length of 1) so is disposed.

EDIT: Also if you want to update all local parameters (xPosition and yPosition) without using keys you should do it inside the didUpdateWidget method

@override
  void didUpdateWidget(MovableItem oldWidget){
    print('updated: oldWidget ${oldWidget.removeIndex} vs newWidget ${widget.removeIndex}');
    xPosition = widget.xInit;
    yPosition = widget.yInit;
    super.didUpdateWidget(oldWidget);
  }

that way the positions will update when you delete one of the widgets from the list (if you don't want to use keys)

Related