1

I have the following code where I generate a list of items(data is taken from Firebase). I would like to implement a functionality to remove items but I don't know how to access the list and how to remove items:

class _MyOfferState extends State<MyOffer> {
  List<Widget> items = [];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
...
          body: SingleChildScrollView(
            child: Column(
              children: [
                StreamBuilder<QuerySnapshot>(
                  stream: FirebaseFirestore.instance
                      .collection('Offers')
                  builder: (BuildContext context, snapshot) {
                      snapshot.data.docs.forEach((element) {
                        element.get('items').forEach((item) {
                          String _name = element['name'];
                          String _category = item['category'];

                          items.add(offer(name, category, context,...));

                        });
            }
                      );
                    }
                    return new Column(
                      children: List.unmodifiable(() sync* {
                        yield* items;
                      }()),
                    );
                  },
          ),
        
  }
}

This is a dynamic class where I have GestureDetector. The item should be deleted when a user clicks on the it.

dynamic offer(name, category, context,) {

  return GestureDetector(
      child: Container(
        child: Row(
                    children: [
                      Text(name),
                      Text(category),
          ],
        ),
      ),
    ),
    onTap: () {
      
 // remove item should be here
    },
  );
}
user1209304
  • 408
  • 1
  • 5
  • 26
  • Have you managed to display the data in a listview? If not, try creating a listview.builder https://flutter.dev/docs/cookbook/lists/long-lists – James 666 May 18 '21 at 23:12
  • If you want to remove a specific item from a list, you call items.removeAt(index). Let us know if you need it to be more specific than this - what exactly are you trying to remove from your list? – James 666 May 18 '21 at 23:16
  • From the class dynamic offer I need somehow identify which item in the list was clicked, then remove this item from the list and then rebuild SingleChildScrollView – user1209304 May 19 '21 at 08:08
  • Ok, use a listview.builder to display the list of your items and in the itemBuilder argument you can find the item which was clicked through: (context, index){ return GestureDetector(child: Container(), onTap: (){ print(items[index]) } ) } – James 666 May 19 '21 at 10:45
  • Just to note, don't ever rebuild an entire SingleChildScrollView with all of its widgets inside it - in Flutter you only need to rebuild widgets that have state changes. If you rebuild an entire SingleChildScrollView, you will get jank and it will create a very bad UX – James 666 May 19 '21 at 10:48
  • 1
    From your original structure, you want to delete the data **on FirebaseFirestore**, right? The Stream already listens to any change on Firestore and rebuilds itself. – yellowgray May 21 '21 at 06:02
  • I want to delete data from FirebaseFirestore and rebuild UI to display the correct list of items. – user1209304 May 21 '21 at 07:33
  • I think what you're looking for is two `SliverList` inside a `CustomScrollView` so that you can have two different lists that scroll together. The first one would need to be a SliverList with a builder delegate. https://www.youtube.com/watch?v=ORiTTaVY6mM – CoderUni May 22 '21 at 13:04

3 Answers3

0

you need to pass index of item and delete by index:

int index = 0;
snapshot.data.docs.forEach((element) {
                        element.get('items').forEach((item) {
                          String _name = element['name'];
                          String _category = item['category'];

                          items.add(offer(index, name, category, context,...));
                          index++;
                        });



Widget offer(int index, string name, string category, BuildContext context,) {

  return GestureDetector(
      child: Container(
        child: Row(
                    children: [
                      Text(name),
                      Text(category),
          ],
        ),
      ),
    ),
    onTap: () {
      
 // remove item should be here
items.removeAt(index);
setState((){});
    },
  );
}

            }
                      );
                    }
                    return new Column(
                      children: List.unmodifiable(() sync* {
                        yield* items;
                      }()),
                    );
Jim
  • 6,928
  • 1
  • 7
  • 18
  • Thanks for the answer. But I don't know how to call a setState from class "offer". I'm getting error: setState() called in constructor: _MyOfferState#1489b(lifecycle state: created, no widget, not mounted) – user1209304 May 19 '21 at 09:18
  • why dont you make dynamic as a Widget in class _MyOfferState, updated – Jim May 19 '21 at 09:20
  • If I'm doing as you suggested then instead of removing elements I see additional elements on the screen. Looks like it does not remove elements from the List but makes some copy. – user1209304 May 19 '21 at 16:46
0

Your list is getting build by Stream data the one you provided to your StreamBuilder, do create new list you need to change Stream value, I suggest to keep FirebaseFirestore.instance.collection('Offers') instance in a stream and modify the stream.

    class _MyOfferState extends State<MyOffer> {
      List<Widget> items = [];
StreamController _controller = StreamController();

 @override
  void initState() {
    super.initState();
_controller.addStream( FirebaseFirestore.instance
                      .collection('Offers').snapshots());    
  }


// dont forgot to close stream
 @override
  void dispose() {
    _controller.close();
    super.dispose();
  }


      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
    ...
                  body: SingleChildScrollView(
                    child: Column(
                      children: [
                        StreamBuilder<QuerySnapshot>(
                          stream: _controller.stream,
                          builder: (BuildContext context, snapshot) {
                              snapshot.data.docs.forEach((element) {
                                element.get('items').forEach((item) {
                                  String _name = element['name'];
                                  String _category = item['category'];
        
                                  items.add(offer(name, category, context,(){
// remove function is here
  snapshot.data.docs.removeWhere((e) => e.id == element.id);
 _controller.add(snapshot.data);
});
      


                            });
                }
                          );
                        }
                        return new Column(
                          children: List.unmodifiable(() sync* {
                            yield* items;
                          }()),
                        );
                      },
              ),
            
      }
    }

Also pass onTap to your widget function

dynamic offer(name, category, context, onTap) {

  return GestureDetector(
      child: Container(
        child: Row(
                    children: [
                      Text(name),
                      Text(category),
          ],
        ),
      ),
    ),
    onTap: onTap,
  );
}
0

Removing the offer from within itself is not the best practice but you can accomplish it in a number of ways. The first I can think of is to pass a function that removes it when creating the offer like this:

items.add(offer(name, category, context,..., () {
   setState(() {
      FirebaseFirestore.instance
                      .collection('Offers')
                      .doc(element['id'])
                      .delete();
      items.remoev(index);
   });
}));

You'll need to create the index beforehand and increase it each time but I don't recommend doing it. The way I would done do this is change the offer to be:

dynamic offer(name, category, context,) {

  return Container(
        child: Row(
           children: [
              Text(name),
              Text(category),
          ],
        ),
      );
}

And when creating the offer wrap it in the GestureDetector like this:

items.add(GestureDetector(
             child: offer(name, category, context,...)),
             onTap: () {
                setState(() {
                    FirebaseFirestore.instance
                      .collection('Offers')
                      .doc(element['id'])
                      .delete();
                    items.remoev(index);
                });
             },
         );

You'll have to do the same thing with the index but I consider it a better approach since the child has no power over the parent and can't change its state which is a good practice.

egjlmn1
  • 324
  • 3
  • 11