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.
CodePudding user response:
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);
},
),