Minimum reproducible code:
class FooPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncValue = ref.watch(provider1);
print('loading: ${asyncValue.isLoading}, value: ${asyncValue.valueOrNull}');
return Container();
}
}
final provider1 = StreamProvider<int>((ref) {
final stream = ref.watch(provider2);
return stream.maybeWhen(
orElse: () => Stream.value(0),
data: (x) {
print('data = $x');
return Stream.value(x);
},
);
});
final provider2 = StreamProvider<int>((ref) async* {
await Future.delayed(Duration(seconds: 3));
yield 1;
});
Output:
flutter: loading: true, value: null // Why value != 0
flutter: loading: false, value: 0
// After 3 seconds...
flutter: data = 1
flutter: loading: true, value: 0 // Why value != 1
flutter: loading: false, value: 1
There are two questions.
When I have already provided a default value in
orElse
callback, then why the first line doesn't print that value instead of going in the loading state and printingnull
.Once I get the data after 3s, why does the provider print
loading: true, value: 0
?
CodePudding user response:
When a StreamProvider
is first created, it will not have received any data from the stream yet, so its isLoading
property will be true
and its valueOrNull
property will be null
. This is why the first line of the output prints loading: loading: true, value: null
.
Once the stream emits data, the StreamProvider
will update its valueOrNull
property with the latest value from the stream. However, it is possible that the ConsumerWidget
has not yet rebuilt to reflect this change. When the ConsumerWidget
rebuilds, it will receive the latest value from the stream and print it. This is why the fourth line of the output prints loading: false, value: 1
.
The orElse
callback is used to provide a default value for the StreamProvider
when the stream has not emitted any data yet. It does not affect the StreamProvider
after it has received data from the stream. This is why the second and fifth lines of the output print loading: false, value: 0
.
In summary, the StreamProvider
will initially be in the loading state and have a null
value until it receives data from the stream. Once it has received data, it will update its value to reflect the latest value from the stream, but this update may not be reflected in the ConsumerWidget
until it rebuilds. The orElse
callback only provides a default value for the StreamProvider
when the stream has not emitted any data.
CodePudding user response:
provider2
changed (it went from loading to having data). And since provider1
depends on it, provider1
got recomputed.
This ends up creating a new stream. The provider then listens to the new stream. Listening to a stream is an async operation, therefore for one frame provider1
does not have access to the new value exposed by the stream. Hence, you got a loading state.
This shouldn't be a problem though