skip to Main Content

I have a screen that listens for changes in a document using a stream builder. If the document has data then it shows Widget1, otherwise it will show Widget2.

The problem:
When the stream builder builds for the first time it uses Widget2 since there is no data, but when it receives the data it does not update the widget to Widget2.

Here is my simplified implementation:




class Test extends ConsumerStatefulWidget {
  const Test({ Key? key }) : super(key: key);

  @override
  ConsumerState<Test> createState() => _TestState();
}

class _TestState extends ConsumerState<Test> {
  @override
  Widget build(BuildContext context) {
    // final match = ref.watch(matchedEventProvider);
    final user = ref.read(currentUserProvider);

    return Scaffold(
      body:StreamBuilder<Object>(
        stream: db
            .collection(kMatchedCollection)
            .doc(user.currentMatch)
            .snapshots(),
        builder: (context, snapshot) {
          if (snapshot.data != null) {
            kLogger.d('snapshot data: ${snapshot.data.toString()}');
            final snap =
                snapshot.hasData ? snapshot.data as DocumentSnapshot : null;
            matchData = snap?.data() as Map<String, dynamic>;

            kLogger.d("Got real match data: $matchData");
            matchData['CreatedAt'] = DateTime.fromMillisecondsSinceEpoch(
                    matchData['CreatedAt'].seconds * 1000)
                .toString();
            final Event event = Event.fromJson(matchData);
            final String status = event.EventStatus;
            if (event.AssignedTo == ref.read(currentUserProvider).id) {
              //only push new screens if current user is assigned.
              if (status == Status.dateCancelled) {
                Navigator.pop(context);
              } else if (status == Status.dateConfirmed) {
                Navigator.pushReplacementNamed(
                    context, FinalConfirmationScreen.id);
              } else if (status == Status.deciding) {
                Navigator.pushReplacementNamed(
                    context, FinalConfirmationScreen.id);
              }
            }

            return Widget1();
          } else {
            kLogger.d("has data ${snapshot.hasData}");
            return Widget2();
          }
        },
      ),
    );
  }
}

Here are the corresponding logs:

/flutter (  988): ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
I/flutter (  988): │ #0   _LocationWaitScreenState.build.<anonymous closure> (package:app/screens/location_wait_screen.dart:154:21)
I/flutter (  988): │ #1   StreamBuilder.build (package:flutter/src/widgets/async.dart:437:81)
I/flutter (  988): ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
I/flutter (  988): │ 🐛 has data false
I/flutter (  988): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
I/flutter (  988): ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
I/flutter (  988): │ #0   _LocationWaitScreenState.build.<anonymous closure> (package:app/screens/location_wait_screen.dart:44:21)
I/flutter (  988): │ #1   StreamBuilder.build (package:flutter/src/widgets/async.dart:437:81)
I/flutter (  988): ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
I/flutter (  988): │ 🐛 snapshot data: Instance of '_JsonDocumentSnapshot'
I/flutter (  988): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
I/flutter (  988): ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
I/flutter (  988): │ #0   _LocationWaitScreenState.build.<anonymous closure> (package:app/screens/location_wait_screen.dart:49:21)
I/flutter (  988): │ #1   StreamBuilder.build (package:flutter/src/widgets/async.dart:437:81)
I/flutter (  988): ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
I/flutter (  988): │ 🐛 Got real match data: {StartedWith: bCO38DzmjrU9DsT4FTvU7uqR4r42, AssignedTo: bCO38DzmjrU9DsT4FTvU7uqR4r42, DocID: a1776e3d180543b084, Resets: 3, CreatedAt: Timestamp(seconds=1703706807, nanoseconds=365138000), EventStatus: Start, Time: , Participants: [RaLpzb2BZBUnn8AYYJ5UiApmmtx2, bCO38DzmjrU9DsT4FTvU7uqR4r42], Location: }
I/flutter (  988): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

From the logs we can observe that it first builds Widget2 as it says 🐛 has data false and then when the snapshot receives data it tries to build Widget1 but these build changes are not reflected in the UI.

Any help on this would be appreciated

2

Answers


  1. Chosen as BEST ANSWER

    To all the new people stumbling on this question, the problem is with an animation library that I was using. Don't try to modify the Text Widget child inside the animation widget and instead only use one animation widget while conditionally rendering.

    So the code will go from this:

     if (snapshot.hasData) {
                return const Animation(child: Text('Widget 1'));
              } else {
                return const Animation(child: Text('Widget 2'));
              }
            },
    

    to :

     if (snapshot.hasData) {
                return const Animation(child: Text('Widget 1'));
              } else {
                return Text('Widget 2');
              }
            },
    

    Removing the Animationwidget while rendering conditionally fixed this issue for me.

    Note: Here Animation widget could be any generic animation library widget from pub.dev .


  2. Instead of checking if snapshot.data is null. Consider using the function hasData i.e

    StreamBuilder<Object>(
      stream: <DB stream>,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
        // your logic here
          
          return Widget1();
        } else if (snapshot.hasError) {
          // Handle error state if needed
          return ErrorWidget();
        } else {
          kLogger.d("has data ${snapshot.hasData}");
          return Widget2();
        }
      },
    )
    

    Here is a simple example to simulate a stream please try it out and update your code accordingly if it works. If it works it means something is wrong with your ConsumerStateful widget and ConsumerState

    void main() => runApp(const MyApp()); //app entry point
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Simulate Stream',
          theme: ThemeData(
            brightness: Brightness.dark,
            primarySwatch: Colors.blue,
          ),
          home: const Test(), //load home widget
        );
      }
    }
    
    class Test extends StatefulWidget {
      const Test({Key? key}) : super(key: key);
    
      @override
      State<Test> createState() => _TestState();
    }
    
    class _TestState extends State<Test> {
      final StreamController<String> _streamController = StreamController<String>();
    
      void simulateStream() {
        _streamController.add('Data 1');
        // You can add more data as needed
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          floatingActionButton: ElevatedButton(
              onPressed: simulateStream, child: const Text('Add data')),
          body: StreamBuilder<Object>(
            stream: _streamController.stream,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return const Center(child: Text('Widget 1'));
              } else {
                return const Center(child: Text('Widget 2'));
              }
            },
          ),
        );
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search