-
-
Notifications
You must be signed in to change notification settings - Fork 965
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ref.watch returns a value other than ref.read #3889
Comments
I'd need a complete example. As is, this doesn't make sense to me |
Hm.. If I start monitoring the value by setting the listener in the |
My guess is more that your provider got disposed after |
That's the point that both ref.read and ref.watch are called synchronously, and ref.red returns the correct value and ref.watch returns the old value (that was before the notifier action that had been invoked before ref.read and ref.watch). Tricky behavior.
Yes, I understand, unfortunately I can't realize how to simplify the logic to fit the problem into a simple example. Was hoping for any hints.. Anyway, thanks for the quick response. |
Ok, it seems watching is not working properly if the side effect is called before watching. I'm still not sure whether it is a bug or some unevident feature. Flutter exampleimport 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'main.g.dart';
@riverpod
class My extends _$My
{
@override
Future<int> build() async
{
final value = await Future.delayed(
const Duration(milliseconds: 100),
() => _value,
);
return value;
}
Future<void> inc() async
{
final value = await future;
await Future.delayed(
const Duration(milliseconds: 280),
() => _value = value + 1,
);
ref.invalidateSelf();
await future;
}
static int _value = 0;
}
void main()
{
runApp(const ProviderScope(
observers: [ DebugProviderObserver() ],
child: MyApp(),
));
}
class MyApp extends StatelessWidget
{
const MyApp({ super.key });
@override
Widget build(final BuildContext context)
{
return MaterialApp(
theme: ThemeData.light(useMaterial3: true),
darkTheme: ThemeData.dark(useMaterial3: true),
themeMode: ThemeMode.system,
debugShowCheckedModeBanner: false,
home: const Scaffold(
body: MyWidget(),
),
);
}
}
class MyWidget extends ConsumerStatefulWidget
{
const MyWidget({ super.key });
@override
ConsumerState<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends ConsumerState<MyWidget>
{
final deferredValue = Future.delayed(
const Duration(milliseconds: 140),
() => 'initialized',
);
@override
void initState()
{
super.initState();
// Calling the side effect before watching starts.
_change();
}
@override
Widget build(final BuildContext context)
{
return FutureBuilder(
initialData: 'initializing...',
future: deferredValue,
builder: (context, snapshot) => Center(child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(snapshot.data!),
if (snapshot.connectionState == ConnectionState.done) ...[
_buildProvider(),
ElevatedButton(
onPressed: _changing ? null : _change,
child: const Text('Change'),
),
],
],
)),
);
}
Widget _buildProvider()
{
final value = ref.watch(myProvider).valueOrNull;
print('[ui] Showing $value');
return Text('value: $value');
}
Future<void> _change() async
{
setState(() => _changing = true);
try {
print('[ui] Changing');
await ref.read(myProvider.notifier).inc();
} finally {
setState(() => _changing = false);
}
}
var _changing = false;
}
class DebugProviderObserver extends ProviderObserver
{
const DebugProviderObserver();
@override
void didAddProvider(
final ProviderBase<Object?> provider,
final Object? value,
final ProviderContainer container,
)
{
logMessage('Provider ${provider.name} was initialized with $value');
}
@override
void didDisposeProvider(
final ProviderBase<Object?> provider,
final ProviderContainer container,
)
{
logMessage('Provider ${provider.name} was disposed');
}
@override
void didUpdateProvider(
final ProviderBase<Object?> provider,
final Object? previousValue,
final Object? newValue,
final ProviderContainer container,
)
{
logMessage(
'Provider ${provider.name} updated from $previousValue to $newValue'
);
}
@override
void providerDidFail(
final ProviderBase<Object?> provider,
final Object error,
final StackTrace stackTrace,
final ProviderContainer container,
)
{
logMessage('Provider ${provider.name} threw $error at $stackTrace');
}
void logMessage(final String message)
{
print('[riverpod] $message');
}
} Log flutter: [ui] Changing
flutter: [riverpod] Provider myProvider was initialized with AsyncLoading<int>()
flutter: [riverpod] Provider myProvider was disposed
flutter: [riverpod] Provider myProvider was initialized with AsyncLoading<int>()
flutter: [ui] Showing null
flutter: [riverpod] Provider myProvider updated from AsyncLoading<int>() to AsyncData<int>(value: 0)
flutter: [ui] Showing 0
flutter: [riverpod] Provider myProvider updated from AsyncLoading<int>() to AsyncData<int>(value: 1)
flutter: [riverpod] Provider myProvider was disposed
flutter: [riverpod] Provider myProvider was initialized with AsyncLoading<int>()
flutter: [ui] Showing null
flutter: [riverpod] Provider myProvider was disposed |
What's wrong here exactly? Your provider wasn't listener so it got disposed. |
As I understand it, |
If I remove the side effect from @override
void initState()
{
super.initState();
// _change();
} So, the problem is |
You don't watch the provider until 140ms elapse. So it can get disposed before then. Your UI did update, as we see various logs with different values |
Absolutely right. But after this delay shouldn't the |
It does. Hence why there's no dispose between the second The second dispose and reset to |
Right, but |
Listening the provider works correctly when started after calling the provider's side effect, so there is no problem in |
Describe the bug
I'm not sure, but it looks like a bug, and I wonder to know when it can happen, just to understand how to approach this problem.
To Reproduce
Expected behavior
The values are the same.
The text was updated successfully, but these errors were encountered: