Search code examples
flutterdartstreamstream-builderflutter-streambuilder

Change a stream in Streambuilder dynamically


I'm using StreamBuilder to show a group of elements from Firebase in a list. The problem is that I need to filter and update that list changing the stream from Streambuilder. I don't know who can I do that. I read about Streamcontroller and I tried to use it but it throws me an error ("StateError (Bad state: Cannot add new events while doing an addStream)"). Please, I don't know what to do :( Thank you so much!

Here is the important parts of the code:

class _widget_productosState extends State<widget_productos> {
  final User? usuario = FirebaseAuth.instance.currentUser;
  final firestoreInstance = FirebaseFirestore.instance;
  List<dynamic> lista_alergenos = [];
  var nombre_filtro = TextEditingController();
  var nombre = '';
  Stream<QuerySnapshot> stream_datos = FirebaseFirestore.instance.collection('Producto').snapshots();
  StreamController<QuerySnapshot> controller = StreamController<QuerySnapshot>.broadcast();
  
  @override
  void initState() {
    super.initState();
    stream_datos.forEach((element) {
      controller.add(element);
   });
  }
  
  @override
  Widget build(BuildContext context) {
    List<String> filtro_alergenos = [];

    return 
    SafeArea(child:
    Scaffold(
      backgroundColor: Color.fromARGB(255, 239, 241, 245),
      body: StreamBuilder(
        stream: controller.stream,//stream_datos, //nombre == '' ? firestoreInstance.collection('Producto').snapshots() : firestoreInstance.collection('Producto').where('nombre', isEqualTo: nombre).snapshots(),
        builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
          if (!snapshot.hasData) {
            return new Center(child: new CircularProgressIndicator());
          }
      return 
        Stack(children: [
          Column(children:[
          Container(
                child: DecoratedBox(
                  decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(1.0),
                      border: Border.all(
                          color: Colors.grey.withOpacity(0.5), width: 1.0),
                      color: Color.fromARGB(255, 255, 255, 255)),
                  child: Row(
                    children: [
                      IconButton(
                        (...)
                        ),
                        onPressed: () {
                          showDialog(
                            context: context,
                            builder: (_) =>mostrar_filtros(filtro_alergenos)
                          );
                        },
                      ),
                      Expanded(
                        (...)
                      ),
                      IconButton(
                        (...)
                        ),
                        onPressed: () {
                          setState(() {
                            nombre = nombre_filtro.text;
                            if(nombre==''){
                              controller.stream.drain();
                              setState(() {
                                
                                controller.addStream(stream_datos);
                              });
                              //stream_datos = firestoreInstance.collection('Producto').where('nombre', isEqualTo: nombre).snapshots();
                            }else{
                                stream_datos = firestoreInstance.collection('Producto').where('nombre', isEqualTo: nombre).snapshots();
                                controller.stream.drain();
                                controller.addStream(stream_datos);
                              setState(() {
                                print("ey");
                              });
                              //stream_datos = firestoreInstance.collection('Producto').snapshots();
                            }
                          });
                        },
                      ),

(...)


Solution

  • You can do that by not using the StreamController and instead hold a reference to the Stream while you do your filter, such as:

    
    Stream? filterStream;
    String? filterValue;
    
    // you can create a method that performs the filtering for you, such as:
    
    void resetStreamWithNameFilter() {
      
        setState(() {
          
          // return all products if your filter is empty
          if (filter.isEmpty) {
            filterStream = FirebaseFirestore.instance.collection('Producto').snapshots();
            return;
          }
          
          // if the filter is not empty, then filter by that field 'nombre'
          valueStream = FirebaseFirestore.instance.collection('team').where('nombre', isEqualTo: filter).snapshots();
        });
      }
    
    

    Then in your build method, this would be the first method you call; also use the newly created filterStream field, as in:

    @override
    Widget build(BuildContext context) {
        
        // call it initially, to reset the stream
        resetStreamWithNameFilter();
       
        return StreamBuilder(
          stream: filterStream,
          builder: (context, snapshot) {
    
             // do what you want to do here, but trigger the filter accordingly
             List<QueryDocumentSnapshot> docs = (snapshot.data as QuerySnapshot).docs;
             
             return Column(
                children: [
                  Text(docs.length.toString()),
                  TextButton(
                    onPressed: () {
    
                       // for example, filter by all products named 'camisa'
                       filterString = 'camisa';
                       
                       // then, execute the resetStreamWithNameFilter
                       resetStreamWithNameFilter();
                    }
                  )
                ]
             );
          }
        );
    }
    

    I created a Gist so you can get the idea. Run it on DartPad and check it out.