Search code examples
flutterstream-builderflutter-state

How to avoid streambuilder executes unnecessarytimes


I'm trying to display a list of documents which works, but I read that one good practice is to manage states (which I'm trying currently to understand too). In this case every time I change of screen using the bottomNavigationBar the streamBuilder executes (I always see the CircularProgressIndicator). I tried call the collection reference in the intState but still the same issue, my code:

class Deparments extends StatefulWidget {
  Deparments({Key? key, required this.auth}) : super(key: key);
  final AuthBase auth;

  @override
  _DeparmentsState createState() => _DeparmentsState();
}

class _DeparmentsState extends State<Deparments> {
  late final Stream<QuerySnapshot<Object?>> _widget;

 Stream<QuerySnapshot<Object?>> getProds(){
   CollectionReference ref =  FirebaseFirestore.instance.collection("Departamentos");
   return ref.snapshots();
 }
  @override
  void initState() {
    super.initState();
    _widget = getProds();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      drawer: SideMenu(auth: widget.auth),
      appBar: AppBar(
        title: Text("Departamentos"),
        centerTitle: true,
        backgroundColor: Colors.green,
      ),
      body: Container(
        child: StreamBuilder<QuerySnapshot> (
            stream: _widget,
            builder: (BuildContext context, snapshot) {
              if (!snapshot.hasData) {
                return Center(
                  child: CircularProgressIndicator(),
                );
              } else {
                List deparments =
                    snapshot.data!.docs.map((doc) => doc.id).toList();
                return Column(
                  children: [
                    Expanded(
                      child: ListView.builder(
                          padding: EdgeInsets.only(top: 10),
                          scrollDirection: Axis.vertical,
                          itemCount: deparments.length,
                          itemBuilder: (context, index) {
                            return SingleChildScrollView(
                              child: Card(
                                child: Text(deparments[index]),
                              ),
                            );
                          }),
                    )
                  ],
                );
              }
            }),
      ),
    );
  }
}

Update: for those who are facing the same issue Tayan provides a useful solution and he has a video showing the solution https://stackoverflow.com/a/64057210/9429407


Solution

  • Init state will not help you to avoid rebuilds because on changing tabs Flutter rebuilds your Screen. So we need some way to keep our screen alive, so here comes AutomaticKeepAliveClientMixin.

    class _HomeState extends State<Home> with AutomaticKeepAliveClientMixin<Home> {
      @override
      bool get wantKeepAlive => true;
    
      @override
      Widget build(BuildContext context) {
    
        //Make sure to include the below method
        super.build(context);
    
        return SomeWidget();
      }
    }
    

    The above implementation keeps all of your tab state persists and does not rebuilds the tabs again. Well this may serve your purpose but it may not be idle because this loads all the tabs at once even if the user actually didnt visited a tab, so to avoid the build unless a tab is clicked, use the above method in combination with pageview.

    Check out pageView implementation

    Also, if you want a better way to manage state and save some of your read calls to Firestore, then you should store data locally and fetch only those needed and/or use paginations.