Search code examples
flutterdartstream-builder

Flutter StreamBuilder with initialData and null-awareness


I have some problems understanding the StreamBuilder using the new null-aware operators.

As a learning project I am implementing a sign-in flow with the BloC pattern. For my email sign-in form I have created a model class which I access through a StreamBuilder. Without the use of initialData it makes complete sense that snapshot.data can be null. However, setting the initialData to a predefined empty model, snapshot.data could never be null right? Here is my code snippet:

 @override
  Widget build(BuildContext context) {
    return StreamBuilder<EmailSignInModel>(
        stream: widget.bloc.modelStream,
        initialData: EmailSignInModel(),
        builder: (context, snapshot) {
          final EmailSignInModel model = snapshot.data;
          return Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              mainAxisSize: MainAxisSize.min,
              children: _buildChildren(),
            ),
          );
        });
  }

The compiler warns me that snapshot.data is of type <EmailSignModel?> which is not equal to . I could fix this with snapshot.data ?? EmailSignModel() but this would be redundant with initialData right?

What is the proper way of handling this situation and taken care of the null-awareness of Dart?


Solution

  • Diving into the source code, I found the following:

    https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/async.dart

      /// The latest data received by the asynchronous computation.
      ///
      /// If this is non-null, [hasData] will be true.
      ///
      /// If [error] is not null, this will be null. See [hasError].
      ///
      /// If the asynchronous computation has never returned a value, this may be
      /// set to an initial data value specified by the relevant widget. See
      /// [FutureBuilder.initialData] and [StreamBuilder.initialData].
      final T? data;
    
      /// Returns latest data received, failing if there is no data.
      ///
      /// Throws [error], if [hasError]. Throws [StateError], if neither [hasData]
      /// nor [hasError].
      T get requireData {
        if (hasData)
          return data!;
        if (hasError)
          throw error!;
        throw StateError('Snapshot has neither data nor error');
      }
    

    AsyncSnapshot actually has a requireData getter, which will ensure non-null or will throw an error. So just replace snapshot.data with snapshot.requireData

    This still requires some manual work where the use of initialData and requireData need to be kept in sync. You could also just use snapshot.data! which basically does the same.