且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

从列表中删除项目后更新UI

更新时间:2023-12-04 17:18:47


我不想创建我的新实例列表只是为了更新UI。


Flutter使用不可变对象。不遵循此规则会违反反应框架。



事实是,这种不可改变性特别是在防止开发人员执行当前操作时:拥有一个依赖于共享程序的程序类之间对象的相同实例;






真正的问题在于,这是您的列表项删除了



问题是由于是您的项目在进行计算,因此永远不会通知父项列表已更改。因此,它不知道应该重新呈现。因此,视觉上没有任何变化。



要解决此问题,您应该将删除逻辑移到父级。并确保父级相应地正确调用 setState 。这将转化为将回调传递给您的列表项,删除后会调用该回调。



下面是一个示例:

  class MyList扩展了StatefulWidget {
@override
_MyListState createState()=> _MyListState();
}

类_MyListState扩展了State< MyList> {
List< String> list = List.generate(100,(i)=> i.toString());

@override
小部件build(BuildContext context){
return ListView.builder(
itemCount:list.length,
itemBuilder:(context,index ){
return MyItem(list [index],onDelete:()=> removeItem(index));
},
);
}

void removeItem(int index){
setState((){
list = List.from(list)
..removeAt(index );
});
}
}

类MyItem扩展了StatelessWidget {
最终字符串标题;
最终的VoidCallback onDelete;

MyItem(this.title,{this.onDelete});

@override
窗口小部件build(BuildContext context){
return ListTile(
title:Text(this.title),
onTap:this.onDelete ,
);
}
}


I want to update my ListView if i remove or add items. Right now i just want to delete items and see the deletion of the items immediately.

My application is more complex so i wrote a small example project to show my problems.

The TestItem class holds some data entries:

class TestItem {
  static int id = 1;
  bool isFinished = false;
  String text;
  TestItem() {
    text = "Item ${id++}";
  }
}

The ItemInfoViewWidget is the UI representation of the TestItem and removes the item if it is finished (whenever the Checkbox is changed to true).

class ItemInfoViewWidget extends StatefulWidget {
  TestItem item;
  List<TestItem> items;

  ItemInfoViewWidget(this.items, this.item);

  @override
  _ItemInfoViewWidgetState createState() =>
      _ItemInfoViewWidgetState(this.items, this.item);
}

class _ItemInfoViewWidgetState extends State<ItemInfoViewWidget> {
  TestItem item;
  List<TestItem> items;

  _ItemInfoViewWidgetState(this.items, this.item);

  @override
  Widget build(BuildContext context) {
    return new Card(
      child: new Column(
        children: <Widget>[
          new Text(this.item.text),
          new Checkbox(
              value: this.item.isFinished, onChanged: isFinishedChanged)
        ],
      ),
    );
  }

  void isFinishedChanged(bool value) {
    setState(() {
      this.item.isFinished = value;
      this.items.remove(this.item);
    });
  }
}

The ItemViewWidget class builds the ListView.

class ItemViewWidget extends StatefulWidget {
  List<TestItem> items;

  ItemViewWidget(this.items);

  @override
  _ItemViewWidgetState createState() => _ItemViewWidgetState(this.items);
}

class _ItemViewWidgetState extends State<ItemViewWidget> {
  List<TestItem> items;

  _ItemViewWidgetState(this.items);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: new Text('Test'),
      ),
      body: ListView.builder(
          itemCount: this.items.length,
          itemBuilder: (BuildContext context, int index) {
            return new ItemInfoViewWidget(this.items, this.items[index]);
          }),
    );
  }
}

The MyApp shows one TestItem and a button that navigates to the ItemViewWidget page.

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  List<TestItem> items = new List<TestItem>();

  _MyHomePageState() {
    for (int i = 0; i < 20; i++) {
      this.items.add(new TestItem());
    }
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text(widget.title),
        ),
        body: new Column(
          children: <Widget>[
            ItemInfoViewWidget(this.items, this.items.first),
            FlatButton(
              child: new Text('Open Detailed View'),
              onPressed: buttonClicked,
            )
          ],
        ));
  }

  void buttonClicked() {
    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => ItemViewWidget(this.items)),
    );
  }
}

If i toggle the Checkbox of the first item, the Checkbox is marked as finished (as expected), but it is not removed from the UI - however it is removed from the list.

Then I go back to the Main page and I can observe that Item 1 is checked there as well.

So if I go to the ItemViewWidget page again, I can observe that the checked items are no longer present. Based on these observations, I come to the conclusion that my implementation works, but my UI is not updating.

How can I change my code to make an immediate update of the UI possible?

Edit: This is not a duplicate, because

  1. I dont want to create a new instance of my list just to get the UI updated.
  2. The answer does not work: I added this.items = List.from(this.items); but the behavior of my app is the same as already described above.
  3. I don't want to break my reference chain by calling List.from, because my architecture has one list that is referenced by several classes. If i break the chain i have to update all references by my own. Is there a problem with my architecture?

I dont want to create a new instance of my list just to get the UI updated.

Flutter uses immutable object. Not following this rule is going against the reactive framework. It is a voluntary requirement to reduce bugs.

Fact is, this immutability is here especially to prevents developers from doing what you currently do: Having a program that depends on sharing the same instance of an object between classes; as multiple classes may want to modify it.


The real problem lies in the fact that it is your list item that removes delete an element from your list.

The thing is since it's your item which does the computing, the parent is never notified that the list changed. Therefore it doesn't know it should rerender. So nothing visually change.

To fix that you should move the deletion logic to the parent. And make sure that the parent correctly calls setState accordingly. This would translate into passing a callback to your list item, which will be called on deletion.

Here's an example:

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

class _MyListState extends State<MyList> {
  List<String> list = List.generate(100, (i) => i.toString());

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: list.length,
      itemBuilder: (context, index) {
        return MyItem(list[index], onDelete: () => removeItem(index));
      },
    );
  }

  void removeItem(int index) {
    setState(() {
      list = List.from(list)
        ..removeAt(index);
    });
  }
}

class MyItem extends StatelessWidget {
  final String title;
  final VoidCallback onDelete;

  MyItem(this.title, {this.onDelete});

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(this.title),
      onTap: this.onDelete,
    );
  }
}