简体   繁体   中英

How to remove an element from List of Widgets in flutter?

I made a list of TableRows in flutter whichI passed as a children to my Table widget.Each row has some data and a flat button at the end which deletes the data from by database and I want to delete it from that list of TableRows also on the go.I know i have to use change the whole list under setState function but its not actually happening. What happens is it deletes from the database but the my UI remains same but when i go to previous screen and come back to that then its not there.So basically i able to delete it from database but not from UI

class _DataPreviewScreenState extends State<DataPreviewScreen> {
  List<TableRow> listView = [];
  void initState() {
    super.initState();
    finalData = widget.data; //<- final data is my data from Database its actually a Map<String,dynamic>
    listView=listCreater();
  }

  List<TableRow> listCreater() { //<-here im am making the list of TableRow Widgets
    List<TableRow> list = [
      TableRow(
        children: [
          RowHead(heading: 'Site'),
          RowHead(heading: 'UserName'),
          RowHead(heading: 'Password'),
          RowHead(heading: ' '),
        ],
      ),
    ];
    var i;
    for (var data in finalData) {
      var item = TableRow(
        key: UniqueKey(),
        decoration: BoxDecoration(shape: BoxShape.rectangle),
        children: [
          RowCell(data['site_name']),
          RowCell(data['user_name']),
          RowCell(data['password']),
          Container(
            height: 35.0,
            color: Colors.white,
            child: FlatButton(
              onPressed: () {   //<- removing That particular list item from database
                DataModel.instance.delete(data['_id']);
                setState(() {
                  listView.removeAt(i);   /<-trying to delete from UI
                });
              },
              child: Icon(
                Icons.delete,
                size: 20.0,
              ),
            ),
          ),
        ],
      );
      list.add(item);
      i++;
    }

    return list;
  }

Place where i'm passing the rows

Container(
  child: Table(
    columnWidths: {3: FractionColumnWidth(0.1)},
    children: listView,
  ),
),

How the Ui looks currently

这就是 UI 的外观

please help me with the problem or give me any reference to the article or something about keys or any suggestion in my logic like whats the common practise to do it or something. Thank You

listView.removeAt(i);

This line is the source of many errors. Whenever you will fix one, another one will pop up. For starters, you never initialized i . But do not go there. This is a rabbit hole. Removing from that list based on an index that was correct at some time in the past is a fools errand.

Use removeWhere instead and find the correct row by using data (maybe you have an id?), not just an index.

Generally speaking, the concept is to hold the data and rebuild the view. Rebuilding the view is relatively cheap. Removing from the data is easier to accomplish because you have... well... the data at that point.

You probably don't need a StatefulWidget for your DataPreviewScreen .

Instead, you should get the data from your DataModel that, I suppose, is some kind of Provider .

Here is an example using Flutter Hooks and Riverpod :

在此处输入图像描述

My dataProvider provides both the State (a List<Map<String, dynamic>>) and the functionality to delete an entry. I listen to the State in my AppWidget and feed the data to DataPreviewScreen , a pure StatelessWidget .

When the user clicks on a delete button, I ask my dataProvider to delete it and then the new State flows in.

Full source code for easy copy-paste

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:faker/faker.dart';

void main() {
  runApp(ProviderScope(child: AppWidget()));
}

class AppWidget extends HookWidget {
  const AppWidget({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final data = useProvider(dataProvider.state);
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      home: DataPreviewScreen(data: data),
    );
  }
}

class DataPreviewScreen extends StatelessWidget {
  final List<Map<String, dynamic>> data;

  const DataPreviewScreen({Key key, this.data}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Table(
          columnWidths: {
            0: FlexColumnWidth(4),
            1: FlexColumnWidth(4),
            2: FlexColumnWidth(2),
            3: FlexColumnWidth(1),
          },
          children: [
            TableRow(
              children: [
                RowHead(heading: 'Site'),
                RowHead(heading: 'UserName'),
                RowHead(heading: 'Password'),
                RowHead(heading: ' '),
              ],
            ),
            ...data
                .map(
                  (item) => TableRow(
                    key: UniqueKey(),
                    children: [
                      RowCell(item['site_name']),
                      RowCell(item['user_name']),
                      RowCell(item['password']),
                      TextButton(
                        onPressed: () =>
                            context.read(dataProvider).delete(item['_id']),
                        child: Icon(
                          Icons.delete,
                          color: Color(0xFF9F1D35),
                          size: 20.0,
                        ),
                      ),
                    ],
                  ),
                )
                .toList(),
          ],
        ),
      ),
    );
  }
}

class RowHead extends StatelessWidget {
  final String heading;

  const RowHead({Key key, this.heading}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 36.0),
      child: Text(
        heading,
        style: TextStyle(fontWeight: FontWeight.bold),
      ),
    );
  }
}

class RowCell extends StatelessWidget {
  final String text;

  const RowCell(this.text, {Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Expanded(child: Text(text));
  }
}

final dataProvider =
    StateNotifierProvider<DataNotifier>((ref) => DataNotifier());

class DataNotifier extends StateNotifier<List<Map<String, dynamic>>> {
  DataNotifier([List<Map<String, dynamic>> state]) : super(state ?? dummyData);

  delete(String id) {
    // delete from database
    state = state.where((item) => item['_id'] != id).toList();
  }
}

final faker = Faker();
final dummyData = List.generate(
  20,
  (index) => {
    '_id': 'id_${faker.randomGenerator.string(6, min: 6)}',
    'site_name': faker.company.name(),
    'user_name': faker.person.name(),
    'password': faker.randomGenerator.string(8, min: 8),
  },
);

Remarks

  • You should probably define a clear Entity for your Data instead of using a Map<String, dynamic> type. Have a look at freezed .
  • Instead of listening to the State in AppWidget , DataPreviewScreen could also be a HookWidget and both listen to and use the dataProvider .

Instead of trying to remove directly items from the UI, you should instead delete the element from the list and your widget depend on the data.

However, you are missusing the StatefulWidget here.

In general, when we use a StatefulWidget, we fetch the data in the didChangeDependencies() method and update this data in the setState((){}) method. Calling setState() will rebuild the UI (which is cheap) based on the updated data.

Here, your widget data depends on its parent, so:

  • either you have a Stateless widget which depends on the data of its parent, and when the parent data changes, you call setState() on the parent which will rebuild the child with the new data,
  • or you have a StatefulWidget which retrieves the data and updates it in the setState() method.

For instance:

In your didChangeDependencies() method:

_myDataList = DataModel.instance.getMyData();

in your Build() method:

Container(
              child: Table(
                columnWidths: {3: FractionColumnWidth(0.1)},
                children: listCreater(),
              ),
            )

And your listCreater() method:

List<TableRow> listCreater() {
    return [
      TableRow(
        children: [
          RowHead(heading: 'Site'),
          RowHead(heading: 'UserName'),
          RowHead(heading: 'Password'),
          RowHead(heading: ' '),
        ],
      ),
      for (var data in myDataList) 
       TableRow(
        key: UniqueKey(), // This is probably not usefull
        decoration: BoxDecoration(shape: BoxShape.rectangle), // BoxShape.rectangle is the default value, you can also eventually remove this line
        children: [
          RowCell(data['site_name']),
          RowCell(data['user_name']),
          RowCell(data['password']),
          Container(
            height: 35.0,
            color: Colors.white,
            child: FlatButton(
              onPressed: () {
                DataModel.instance.delete(data['_id']);
                setState(() {
                  myDataList.removeWhere((e)=>e.id == data.id);
                });
              },
              child: Icon(
                Icons.delete,
                size: 20.0,
              ),
            ),
          ),
        ],
      )

    ];
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM