Minimum reproducible code:

StreamController<int> _controller = StreamController();
Stream<int> _fooStream() => _controller.stream;

void main() {
  runApp(
    StreamProvider<int>(
      initialData: 0,
      lazy: false,
      create: (_) => _fooStream(),
      child: MaterialApp(
        home: FooPage(),
      ),
    ),
  );
}

class FooPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          child: Text('Get'),
          onPressed: () {
            _controller.add(1); // Added 1 but below line prints initial value
            var value = context.read<int>(); // Prints 0
          },
        ),
      ),
    );
  }
}

The question is, why the value prints 0 instead of 1. How should I read this value properly if it is already not the right way.


Note: I am not looking for workaround like adding Future.delayed(Duration.zero) before retrieving that value or listening for entire StreamProvider by doing context.watch<int>() in the build() method.


Solution 1: Peter Koltai

From the documentation of StreamController.add():

Listeners receive this event in a later microtask. Note that a synchronous controller (created by passing true to the sync parameter of the StreamController constructor) delivers events immediately. Since this behavior violates the contract mentioned here, synchronous controllers should only be used as described in the documentation to ensure that the delivered events always appear as if they were delivered in a separate microtask.

You can set the sync parameter to true when creating the StreamController (keeping in mind the note above from the docs), this will cause value to be 1 in your code after calling _controller.add(1);:

StreamController<int> _controller = StreamController(sync: true);

But if you need the values from the stream to build a widget, you can use Consumer also without setting sync: true, for example to update your button's label:

ElevatedButton(
          child: Consumer<int>(
              builder: (context, value, _) => Text('Get, current is: $value')),
          onPressed: () {
            _controller.add(1);
            var value = context.read<int>();
            print(value);
          },
        ),