Flutter StreamBuilder with initialData and null-awareness

1,151

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.

Share:
1,151
D. Bakhuis
Author by

D. Bakhuis

Updated on December 28, 2022

Comments

  • D. Bakhuis
    D. Bakhuis over 1 year

    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?

    • dumazy
      dumazy about 3 years
      Just wanted to say this is a very interesting question. The fact is that AsyncSnapshot<T> will have a getter data of type T?, so nullability is baked into the AsyncSnapshot class. Maybe this should be reported in the Flutter Github? github.com/flutter/flutter/issues
    • dumazy
      dumazy about 3 years
      I went to have a look in the source code and saw they had a solution to this. Answered below
  • D. Bakhuis
    D. Bakhuis about 3 years
    Thanks for the clear answer. Funny that I was just one ctrl-click away from the doc-string showing part of the answer. All with all, using initialData cannot be null and using requireData is a neat solution. I also like the simple use of ! directly as it is always close to the initialData parameter and making clear it cannot be null.