Search code examples
flutterdartstreamdart-async

Stream listener and streamController "talking" asynchronously not working as expected


I'm trying to understand how to work with Streams starting from the Template flutter code.

the output I'm getting is

I/flutter (32673): MyApp build executing
I/flutter (32673): HomePage constructor executing
I/flutter (32673): MyHomePage createState() _MyHomePageState executing
I/flutter (32673): _MyHomePageState constructor executing
I/flutter (32673): Awaiting anxiously for the speaker to start speaking!
I/flutter (32673): _MyHomePageState build() executing
I/flutter (32673): _MyHomePageState /  floatingActionButton: onPressed executing
I/flutter (32673): _MyHomePageState setState() executing
I/flutter (32673): _MyHomePageState build() executing
I/flutter (32673): _MyHomePageState /  floatingActionButton: onPressed executing

I was expecting something like this:

I/flutter (32673): MyApp build executing
I/flutter (32673): HomePage constructor executing
I/flutter (32673): MyHomePage createState() _MyHomePageState executing
I/flutter (32673): _MyHomePageState constructor executing
I/flutter (32673): Awaiting anxiously for the speaker to start speaking!
I/flutter (32673): _MyHomePageState build() executing
I/flutter (32673): _MyHomePageState /  floatingActionButton: onPressed executing
I/flutter (32673): _MyHomePageState setState() executing
I/flutter (32673): _MyHomePageState build() executing
I/flutter (32673): Topic1 loud and clear
I/flutter (32673): Topic2 loud and clear
I/flutter (32673): Great topic1. APPLAUSE!!!!
I/flutter (32673): Topic3 loud and clear
I/flutter (32673): Topic4 loud and clear
I/flutter (32673): Topic5 loud and clear
I/flutter (32673): Great topic2. APPLAUSE!!!
I/flutter (32673): _MyHomePageState /  floatingActionButton: onPressed executing
I/flutter (32673): Topic6 loud and clear
I/flutter (32673): Great topic3. APPLAUSE!!!

here is the code I'm using to understand Streams... What am I doing wrong?

import 'package:flutter/material.dart';
import 'package:streams/speaker.dart';
import 'package:streams/listener1.dart';

void main() {
  runApp(MyApp());
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print ('MyApp build executing');
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
class MyHomePage extends StatefulWidget {
  final String title;
  MyHomePage({Key? key, required this.title }) : super(key: key) {
    print('HomePage constructor executing');
  }
  @override
  _MyHomePageState createState() {
    print('MyHomePage createState() _MyHomePageState executing');
    return _MyHomePageState();
  }
}
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  _MyHomePageState() : super(){
    print('_MyHomePageState constructor executing');
    var listener1 = NumberListener();
    var speaker = NumberSpeaker();
    listener1.listen();                      // Start listening
    speaker.speak();                         // Start speaking
  }
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
  @override
  Widget build(BuildContext context) {
    print('_MyHomePageState build() executing');
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          print('_MyHomePageState /  floatingActionButton: onPressed executing');
          _incrementCounter();
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

import 'package:streams/speaker.dart';

import 'dart:async';
class NumberSpeaker {
  final StreamController _streamController = StreamController<int>();
  Stream<int>? get stream {
    return _streamController.stream as Stream<int>;
  }
  int _topicNumber = 1;
  int _numberOfTopics = 20;
  speak() async* {
    while (_topicNumber != _numberOfTopics) {               //Speak all 20 topics
      yield _topicNumber;                                   //yield 1 topic at a time
      print('Topic$_topicNumber loud and clear');
      _topicNumber = _topicNumber + 1;
    }
    _streamController.close();
  }
}

import 'package:streams/listener1.dart';

import 'dart:async';
import 'package:streams/speaker.dart';
class NumberListener {
  final speakerStream = NumberSpeaker().stream;
  Future<void> listen() async {
    print('Awaiting anxiously for the speaker to start speaking!');
    await for (var topic in speakerStream!) {                         //wait for the speaker to start
      var wholeSpeach = speakerStream!.listen(
        (topic) {
          print('Great topic$topic. APPLAUSE!!!!');
        },
        onError: (err) {
          print('Error: $err');
        },
        cancelOnError: false,
        onDone: () {
          print('Thunderous  APPLAUSE!!!!!');
        }
      );
    }
  }
}

Solution

  • final speakerStream = NumberSpeaker().stream;
    

    This line connects to a new instance of NumberSpeaker. You have two NumberSpeaker instances, one you are listening to and the other that is actually speaking.

    The listener should instead take the stream to listen to as a constructor parameter, so you can pass in the stream of the actual speaker instance you have.

    For example:

    class NumberListener {
      final Stream<int> speakerStream;
    
      NumberListener(this.speakerStream);
    

    and then when you create them:

    var speaker = NumberSpeaker();
    var listener1 = NumberListener(speaker.stream);
    
    listener1.listen();
    speaker.speak();
    

    For some reason you are returning a nullable stream from your getter. Fix that. There is no point in returning a nullable stream, it should be just a stream.

    Also, take into account what psink wrote in the comments.