skip to Main Content

I am watching a simple StreamProvider which just emits an int value. MyWidget is building only once if I am not assigning anything to theme variable but if I assign anything to theme then widget builds around 12 times.

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends ConsumerWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final provider = ref.watch(streamProvider);
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        textButtonTheme: TextButtonThemeData(
          style: OutlinedButton.styleFrom(foregroundColor: Colors.red),
        ),
      ),
      home: const MyWidget(),
    );
  }
}

class MyWidget extends StatelessWidget {
  const MyWidget({super.key});

  @override
  Widget build(BuildContext context) {
    print("@@@@ build MyWidget");
    return Container(
      color: Theme.of(context).primaryColor,
    );
  }
}

final streamProvider = StreamProvider.autoDispose(((ref) => Stream.value(1)));

This is printing @@@@ build MyWidget 12 times. If I do not read anything from Theme.of then it prints only once. What could be the issue here?

Edit

Everyone is saying it is the problem with Theme.of(context) but my confusion is why it is building only once if I convert ref.watch to ref.read?

6

Answers


  1. This is because of using StreamProvider.autoDispose which triggers the rebuilds in widgets that are using the stream.

    You can use StreamProvider without .autoDispose which will only rebuild the widget that is listening to the stream changes.

    final streamProvider = StreamProvider(((ref) => Stream.value(1)));
    
    Login or Signup to reply.
  2. As far as I can tell, this is not about Riverpod. You can remove any Riverpod dependencies and the problem will remain.

    Also, I’ve narrowed down the problem a bit. Take a look at this code:

    import 'package:flutter/material.dart';
    
    void main() => runApp(const MyApp());
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
      @override
      Widget build(BuildContext context) {
        print('#build $MyApp');
    
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            textButtonTheme: TextButtonThemeData(
              style: TextButton.styleFrom(elevation: 1),
            ),
          ),
          home: Builder(
            builder: (context) {
              print("#build Builder");
    
              Theme.of(context);
    
              return const SizedBox();
            },
          ),
        );
      }
    }
    

    The problem appears whenever we define a new style in the line:

    style: TextButton.styleFrom(elevation: 1),

    If we write TextButton.styleFrom(), everything works as expected. Also, if we don’t use Theme.of(context), that’s fine too.

    It is probably necessary to open issue 🙂

    Login or Signup to reply.
  3. It is combination of many things.

    1. By making MyApp’s build dependent on streamProvider, you are saying that every time there is a value from stream, rebuild the widget tree. (Remove ref.watch and you will have only one build)
    2. Having Theme widget as decedent of MyApp and using Theme.of(context) inside MyWidget you are saying that whenever there is change in context, MyWidget should be rebuilt. (Remove Theme.of(context) while keeping ref.watch and you will have only 1 build)

    Every time a event is emitted, there are other widgets (part of build function of MaterialApp which gets rebuilt. AnimatedTheme, AnimatedTheme and MyWidget were marked dirty by framework in this case. You can verify it yourself by adding print('marking $this as dirty'); to flutter’s framework.dart markNeedBuild method’s end.

    I think there is nothing wrong with either riverpod or flutter.

    Login or Signup to reply.
  4. Have you considered the use of ref.listen instead of ref.watch? with ‘listen’ you can compare the new emitted value with the old, and SetState only if there is a change…

    Login or Signup to reply.
  5. may stream is being re created or you try to assign value multiple times

    Login or Signup to reply.
  6. sure you can try

     import 'package:cloud_firestore/cloud_firestore.dart';
        
        final CollectionReference myCollection = FirebaseFirestore.instance.collection('myCollection');
        
        // Document reference for the document you want to delete
        final DocumentReference documentReference = myCollection.doc('documentId');
        
        // Call delete() on the DocumentReference to delete the document
        documentReference.delete();
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search