Search code examples
flutterdartstream-builderflutter-http

StreamBuilder: A non-null value must be returned since the return type 'Stream' doesn't allow null


I'm using a StreamBuilder widget to show the score in real-time (fetching the data from API and then passing it to the stream) but while doing this process, it is throwing up the following error

"A non-null value must be returned since the return type 'Stream' doesn't allow null"

Here is the Code

class Match extends StatefulWidget {

  @override
  State<Match> createState() => _MatchState();
}

class _MatchState extends State<Match> {
  
    getCricketScoreApi() async {
    var url = Uri.parse(api_url);
    var response = await get(url);
    if(response.statusCode == 200){
      final score = jsonDecode(response.body);
      return  score['cricket_score'];
    } else {
      return print('Something went wrong');
    }
  }

  Stream streamScore() { 
    return Stream.periodic(Duration(seconds: 1), (Timer) => getCricketScoreApi()); // Calling method after every 1 second
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[200],
      body: StreamBuilder(
          stream: streamScore(),
          builder: (context, snapshot){
              var cricket_score_api = snapshot.data.toString();
              if(snapshot.hasData){
                return Center(
                  child: Text(cricket_score_api, style: TextStyle(color: Colors.grey, fontSize: 25.0)), // Error: Instance of Future
                );
              } else {
                return Text('Null');
              }
          }
      ),
    );
  }
}

Solution

  • So as you say, the original error came from not returning a value.

    The current error is even simpler, you ARE returning a value, but that value is, an instance of Future, so when you call snapshot.data.toString();, you get the literal text instance of Future

    I came up with two ways of fixing the issue, here they are now:

    1. Make your stream function an async* function:

    Stream streamScore() async* {
      while (true) {
        await Future.delayed(Duration(seconds: 1));
        yield await getCricketScoreApi();
      }
    }
    

    async* functions work like async functions, but instead of returning a future, they return a stream immediately and add values to the stream progressively using the yield keyword.

    The one thing I don't love about the approach above is the usage of while (true), it seems unsafe.

    2. Deal with the future

    Stream<Future> streamScore() {
      ...
    }
    ...
    body: StreamBuilder<Future>(
        stream: streamScore(),
        builder: (context, snapshot){
            if(snapshot.hasData) {
              return FutureBuilder(
                future: snapshot.data!
                builder: (context, snapshot1) {
                  if (snapshot.hasData) {
                    return Center(child: Text(snapshot1.data!.toString()));
                  } 
                  return CircularProgressIndicator();
                }
              );
            } else {
              return Text('Null');
            }
        }
    ),
    

    Hopefully one of the above solutions fix the new issue?