skip to Main Content

I have a StreamBuilder and an int To increment number to do that I use setState function. And the problem is the StreamBuilder always getting new data every time setState is accur is there anyway to make the StreamBuilder not affect by setState every time the setState accur. To summarise is that Stop StreamBuilder from getting new data from backend every time when using setstate

full code

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';

class StreamBuildTest2 extends StatefulWidget {
  const StreamBuildTest2({super.key});

  @override
  State<StreamBuildTest2> createState() => _StreamBuildTest2State();
}

class _StreamBuildTest2State extends State<StreamBuildTest2> {
  String userUid = FirebaseAuth.instance.currentUser!.uid;
  final FirebaseStorage storage = FirebaseStorage.instance;
  //Variable to increment number
  int number = 0;
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      //The StreamBuilder that’s getting new data every time setState accur 
      body: StreamBuilder<DocumentSnapshot>(
        stream: FirebaseFirestore.instance
            .collection('users')
            .doc(userUid)
            .snapshots(),
        builder: (context, snapshot) {
          //Data from firebase backend
          var fullName = snapshot.data?.get('fullName');
          var email = snapshot.data?.get('email');
          var profileUrl = snapshot.data?.get('profileUrl');
          //
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Container();
          }
          return Column(
            children: [
              //To get user profile
              FutureBuilder(
                future: downloadURL(profileUrl),
                builder:
                    (BuildContext context, AsyncSnapshot<String> snapshot) {
                  if (snapshot.connectionState == ConnectionState.done &&
                      snapshot.hasData) {
                    return SizedBox(
                      height: 110,
                      width: 110,
                      child: FittedBox(
                        fit: BoxFit.contain,
               //The user profile always getting new data every time setState accur
                        child: CircleAvatar(
                          backgroundColor: Colors.transparent,
                          backgroundImage: NetworkImage(snapshot.data!),
                          radius: 10.0,
                        ),
                      ),
                    );
                  }
                  if (snapshot.connectionState == ConnectionState.waiting ||
                      snapshot.hasData) {
                    return Container();
                  }
                  return Container();
                },
              ),
              Text(fullName),
              Text(email),
              Text(number.toString()),
             //Here’s the setState
              ElevatedButton(
                onPressed: () => setState(() {
                  number += 1;
                }),
                style: ElevatedButton.styleFrom(
                  foregroundColor: Colors.black,
                  backgroundColor: Colors.grey,
                  elevation: 0.0,
                  shape: const RoundedRectangleBorder(
                    borderRadius: BorderRadius.zero,
                  ),
                ),
                child: const Text('SetState Button'),
              ),
            ],
          );
        },
      ),
    );
  }
 //Doesn’t matter here
  Future<String> downloadURL(String file) async {
    try {
      String downloadURL =
          await storage.ref('usersProfile/$file').getDownloadURL();
      print(downloadURL);
      return downloadURL;
    } on FirebaseException catch (e) {
      print(e);
    }
    return downloadURL(file);
  }
}

feel free to Modified my code

2

Answers


  1. Since you have the Firestore API call in the build method of your widget, it gets executed each time the widget gets (re)rendered. While this works, it is indeed wasteful.

    So what you’ll want to do instead is:

    1. Call the Firestore API once from the initState method of your widget.
    2. Put the Stream you get back from the snapshots() call there, into the state.
    3. Then use the stream from the state in your build method.

    Randal Schwartz also recorded a great video explaining, so I recommend checking that out: Fixing a common FutureBuilder and StreamBuilder problem

    Login or Signup to reply.
  2. You can also use ValueNotifier and ValueListenableBuilder to avoid rebuilding your entire widget to a _counter notifier, you can try that dartpad example

    https://dartpad.dev/?id=1e52b13f6a9727e541baa74b58b1b558

    // Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
    // for details. All rights reserved. Use of this source code is governed by a
    // BSD-style license that can be found in the LICENSE file.
    
    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          debugShowCheckedModeBanner: false,
          theme: ThemeData(
            colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
            useMaterial3: true,
          ),
          home: const MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      final String title;
    
      const MyHomePage({
        Key? key,
        required this.title,
      }) : super(key: key);
    
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      final ValueNotifier<int> _counterNotifier = ValueNotifier(0);
    
      void _increment() => _counterNotifier.value = _counterNotifier.value + 1;
      void _decrement() => _counterNotifier.value = _counterNotifier.value - 1;
    
      @override
      Widget build(BuildContext context) {
        debugPrint('this does not re-render');
        return Scaffold(
          appBar: AppBar(
            backgroundColor: Theme.of(context).colorScheme.inversePrimary,
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Text(
                  'You have pushed the button this many times:',
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    TextButton(
                      onPressed: _decrement,
                      child: const Icon(Icons.remove),
                    ),
                    SizedBox(
                      width: 48,
                      child: Center(
                      child: ValueListenableBuilder(
                        valueListenable: _counterNotifier,
                        builder: (_, count, __) {
                          return Text(
                            '$count',
                            style: Theme.of(context).textTheme.headlineMedium,
                          );
                        },
                      ),
                       ),
                    ),
                    TextButton(
                      onPressed: _increment,
                      child: const Icon(Icons.add),
                    ),
                  ],
                ),
              ],
            ),
          ),
        );
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search