Jamilur Rahman
Jamilur Rahman

Reputation: 69

Flutter Nested Stream builder -- last stream is getting null before showing data

I am trying to make an app where I need a nested stream builder. The Stream builder looks something like this. but the widgets are built before the last stream is loaded so I get error calling getter null,

StreamBuilder(
  stream: some_stream,
  builder: (context, data){
      return StreamBuilder(
         stream: some_stream,
         builder: (context, data){
             return StreamBuilder(
                stream: some_stream,
                builder: (context, data){

                    return someWidget;

              }

            );
          }
        );
       }
    );

Upvotes: 0

Views: 2323

Answers (2)

Jay Mungara
Jay Mungara

Reputation: 7150

You should always approach the structure for the StreamBuilder like,

StreamBuilder(
  stream: some_stream,
  builder: (context, data){
      return StreamBuilder(
         stream: some_stream2,
         builder: (context, data){
             if(data.hasError) {
               return Text("Error Occured!!");
             } else if(data.hasData) {
               return StreamBuilder(
                      stream: some_stream,
                      builder: (context, data){
                         if (data.hasError){
                            return Text("Error Occured!!");
                         } else if (data.hasData) {
                            return someWidget;
                         }else {
                            return CircularProgressIndicator();
                         }
                      }
               );
             } else {
               return CircularProgressIndicator();
             }
          }
        );
       }
    );

This will save you from errors for most of the time.

Upvotes: 0

Daniel V.
Daniel V.

Reputation: 1198

It is entirely possible that StreamBuilder.builder will be called while the Stream has no data, hasn't connected to the Stream yet or the value is null.

It is up to you to make sure that you handle all these cases.

To make sure that the initial value is never null, you can set inialData.

Future<String> someFutureString = Future.value('initial data seeded');

new StreamBuilder<String>(
  initialData: await someFutureString,
  builder: (ctx, snapshot) { /* ... */ }
);

This is bad practice though. It is better to build such builder that consider the state of the snapshot it works with. Building the widget tree should be quick. Imagine having to wait 3 seconds to the initialData. Your widget tree building will be blocked by at the first await.

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

wrapInMaterialApp(Widget widget) => MaterialApp(
      home: widget,
    );

main() {
  testWidgets('can await initial data', (WidgetTester tester) async {
    final initialData = Future<String>.value('initial value');
    final stream = Stream.fromIterable(['first']);
    final sb = StreamBuilder<String>(
      initialData: await initialData,
      builder: (ctx, snapshot) {
        return Text('${snapshot.data}');
      },
    );
    await tester.pumpWidget(wrapInMaterialApp(sb));
    // Verify that initial data is present
    expect(find.text('initial value'), findsOneWidget);
  });

  testWidgets('can return subtree if there is data', (WidgetTester tester) async {
    final stream = Stream.fromIterable(['first']);
    final sb = StreamBuilder<String>(
      stream: stream,
      builder: (ctx, snapshot) {
        if (snapshot.hasData) {
          return Text('${snapshot.data}');
        } else
          return Container();
      },
    );
    var wrappedWidget = wrapInMaterialApp(sb);
    await tester.pumpWidget(wrappedWidget);

    expect(find.byType(Container), findsOneWidget);
    expect(find.text('first'), findsNothing);

    await tester.pump();

    expect(find.byType(Container), findsNothing);
    expect(find.text('first'), findsOneWidget);
  });
}

Other things that can help you determine what Widget your builder should return is ConnectionState, accessible through snapshot.connectionState.

Cheers!

Upvotes: 1

Related Questions