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
FutureProvider disposed unexpectedly in 1.0.0-dev3 #601
Comments
I'm also experiencing this, with v1.0.0-dev6 |
Same with final messageListBoxFamily =
FutureProvider.family.autoDispose((ref, tribuId) async {
final secureStorage = ref.read(secureStorageProvider);
final hiveKey = await secureStorage.read(key: 'hiveKey') ?? ''; // The error is triggered here
final encryptionKey = base64Url.decode(hiveKey);
Hive.registerAdapter(MessageAdapter());
final box = await Hive.openBox<Message>('${tribuId}_messageList',
encryptionCipher: HiveAesCipher(encryptionKey));
return box;
}); |
I am running into the same problem in hooks_riverpod: ^1.0.0-dev.7 I get an exception like this: Any plans on this? |
@rrousselGit Technically a future provider could be disposed before being used. (Opening a page where it is used and then pressing back before the future completes). So is this really 'a bad state'? Not sure if that is the issue reported here or if it is something else here. @PapyElGringo can you try using |
This is a known bug that I will fix in due time This error is necessary to stop provider.future. But it shouldn't be considered as uncaught |
I have the same issue, any solution? |
I also ran into this in one of my projects which was a command line app where I was using a container and not wanting to listen to the future just read the future. I solved it by not making my provider autodispose for now, but depending on your use case that might not be a long-term or good solution. |
This issue now exists in the released version. ref.watch instead of ref.read worked for me. |
Facing the same issue on 1.0.0 stable release |
I found a work around for now until the fix is out this would work if you only use ref.read (not ref.watch or .when method) to get a value since it is now possible to "await" all providers that emit an AsyncValue you can use "Provider" instead of "FutureProvider" instead of
use
|
Faced the this issue. I solved it by not making my provider autoDispose for now. |
@rrousselGit I think the reason is, that the single subscriptions aren't considered when closing the provider, although they are still waiting for the response: But then I get an error |
This change is incorrect. The only reason this could potentially "solve" the problem in your case is because it broke autoDispose by preventing the provider from being disposed (since it now thinks that it is being listened). I'll look into it later, but I doubt that this error is still a thing on the latest versions. It should've been fixed when I added a |
This bug prevents usage of final future = FutureProvider.autoDispose.family<Response, Request>((ref, req) {
ref.maintainState = true;
Future.delayed(cacheDuration, () => ref.maintainState = false);
return service.call(req);
}); Disabling |
Ok thanks for clarification and the really quick reply. |
@golane-august Your case is expected. The provider was indeed aborted before the future emitted any value. |
I should've realized it sooner, but in the reproduction example given, the exception is indeed expected. Your issue is that you're not listening to the provider when it's marked as autoDispose. To begin with, the code given in the example is not the proper way to make such architecture. Here's the fixed code: import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() {
runApp(ProviderScope(child: MaterialApp(home: MyHomePage())));
}
final controllerProvider = StateNotifierProvider<Controller, List<String>>(
(ref) => Controller(),
);
final futureProvider =
FutureProvider.autoDispose.family<String, String>((ref, str) async {
final ret = await Future.delayed(Duration(seconds: 1), () => str);
ref.maintainState = true;
return ret;
});
class Controller extends StateNotifier<List<String>> {
Controller() : super([]);
void add(String str) {
state = [str, ...state];
}
}
class MyHomePage extends ConsumerWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final items = ref.watch(controllerProvider);
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
ref.read(controllerProvider.notifier).add("example");
},
child: const Icon(Icons.add),
),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final str = items[index];
return FutureBuilder(
future: ref.watch(futureProvider(str).future),
builder: (context, snapshot) {
if (snapshot.hasError) {
return ListTile(
title: Text("${snapshot.error}"),
);
}
if (!snapshot.hasData) {
return const ListTile(
title: Center(child: CircularProgressIndicator()),
);
}
return ListTile(
title: Text("${snapshot.data}"),
);
},
);
},
),
);
}
} |
@rrousselGit So |
I didn't say "never use read". But there is a reason the doc is saying "prefer watch" and "avoid read" 😜 |
Sure 😅 , but for this case, I would say it. It should be disallowed by the library to use it on autodisposable futures, if it's not supposed to. For me its important to make it as predictable as possible, especially with errors. |
Using If we were to use your logic, |
In any case this error is expected, so I'll close this issue for now. If you think this is wrong, please share another reproducible example for this issue. One where the error isn't caused by a a misuse of Riverpod |
@rrousselGit here are two tests where the first passes and the second fails because the provider was unexpectedly disposed. I'm new at Riverpod, Dart, and Flutter, so apologies if I've missed something obvious. In general, I've been struggling with how to unit test StreamProviders in Dart vs widget tests. import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod/riverpod.dart';
Stream<int> countGenerator(int count) {
return Stream<int>.periodic(const Duration(microseconds: 10), (x) => x)
.take(count);
}
final counterStreamProvider = StreamProvider<int>((ref) async* {
await for (final item in countGenerator(10)) {
yield item;
}
});
final counterDisposingStreamProvider =
StreamProvider.autoDispose<int>((ref) async* {
await for (final item in countGenerator(10)) {
yield item;
}
});
void main() {
var container = ProviderContainer();
setUp(() {
container = ProviderContainer();
});
tearDown(() => container.dispose());
void intListener(AsyncValue<int>? from, AsyncValue<int> to) {
expect(to, isNotNull);
}
test('StreamProvider test', () async {
final streamSubscription =
container.listen(counterStreamProvider, intListener);
final result = await container.read(counterStreamProvider.future);
expect(result, isNotNull);
streamSubscription.close();
});
test('Disposing StreamProvider test', () async {
final streamSubscription =
container.listen(counterDisposingStreamProvider, intListener);
final result = await container.read(counterDisposingStreamProvider.future);
expect(result, isNotNull);
streamSubscription.close();
});
} |
Same story here, you probably don't want to use test('Disposing StreamProvider test', () async {
final sub = container.listen(counterDisposingStreamProvider.future, (prev, value) {});
final result = await sub.read();
expect(result, isNotNull);
}); Doing this will keep the provider until the subscription is closed. |
@rrousselGit The I hadn't thought to read using the subscription and not the ProviderContainer. If I've got this right, the listen creates an instance of the provider which I access using the subscription; whereas, when I do a read I get a new, ephemeral instance which is disposed of immediately. If it's useful, I'm happy to turn this into something for the docs once I get it right. |
Shouldn't be. Maybe you forgot to pass the generic type of to specify ".future" |
It doesn't create a new instance. It only subscribes to the cache The problem is with "read", which does not subscribes to the cache. So since it's an autoDispose provider and it's not listened, the cache gets destroyed before the future resolved. |
Thanks so much for the explanation. I did forget to specify future. I switched the test to use streams. I'm still puzzled by why the non-autodispose test sees the right stream while the autodispose provider test fails with a closed stream, but I'm also feeling like I've used enough of your time. import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod/riverpod.dart';
const listLength = 10;
Stream<int> countGenerator(int count) {
return Stream<int>.periodic(
const Duration(microseconds: listLength),
(x) => x,
).take(count);
}
final counterStreamProvider = StreamProvider<int>((ref) async* {
await for (final item in countGenerator(listLength)) {
yield item;
}
});
final counterDisposingStreamProvider =
StreamProvider.autoDispose<int>((ref) async* {
await for (final item in countGenerator(listLength)) {
yield item;
}
});
void main() {
var container = ProviderContainer();
setUp(() {
container = ProviderContainer();
});
tearDown(() => container.dispose());
void listener(Object? from, Object to) {
expect(to, isNotNull);
}
void emitTest(ProviderSubscription<Stream<int>> subscription) {
expect(
subscription.read(),
emitsInOrder(List<int>.generate(listLength, (index) => index)),
);
}
test('StreamProvider test', () async {
final streamSubscription = container.listen(
counterStreamProvider.stream,
listener,
);
// Test passes
emitTest(streamSubscription);
streamSubscription.close();
});
test('Disposing StreamProvider test', () async {
final streamSubscription = container.listen(
counterDisposingStreamProvider.stream,
listener,
);
// Test fails with:
// Actual: <Instance of '_AsBroadcastStream<int>'>
// Which: emitted x Stream closed.
// which didn't emit an event that <0>
emitTest(streamSubscription);
streamSubscription.close();
});
} |
You've refactored the code to use The problem is, As such writing: final sub = container.listen(...);
expect(sub.read(), emits(value));
sub.close(); is strictly equivalent to writing: final sub = container.listen(...);
sub.close();
expect(sub.read(), emits(value)); which should look obviously wrong. You probably want to use final sub = container.listen(...);
await expectLater(sub.read(), emits(value));
sub.close(); |
I put the working example into this gist: riverpod_stream_provider_test.dart. Thank you for all the good work you do. My testing world is much better now. |
I've been keeping an eye on this issue for a while in hopes of a solution to an issue I've been having, but I'm not sure how to avoid using ref.read in this situation: @override
void initState() {
super.initState();
...
context
.read(userComicFamily(widget.comicId).last)
.then((userComicDoc) async {
final currentPageId = userComicDoc?.data()?.currentPageId;
// Get ref to current page once for centering pages on start
final currentPageRef = currentPageId != null
? context.read(sharedComicPageRefFamily(SharedComicPageInfo(
comicId: widget.comicId, pageId: currentPageId)))
: null;
if (currentPageRef != null) {
// Centre on current page
_centerPagesOnRef(currentPageRef);
} else {
final firstPage =
await context.read(endPageFamily(SharedComicPagesQueryInfo(
comicId: widget.comicId,
descending: false,
)).future);
// Start at first page if no current page
if (firstPage != null) {
_centerPagesOnDoc(firstPage);
} else {
_getPages(_ScrollDirection.none);
}
}
});
} I just need to read a few autoDispose future providers once on page open. This works fine in 0.14.0+3 |
Remove the stateful widget. Move the initState logic in a separate FutureProvider which uses ref.watch And watch that FutureProvider |
…suggestion implemented
what if we want to synchronously get a futureprovider value in a asyncnotifier custom method. Currently it requires that the futureprovider actually is used in a widget, but that seems like an odd architecture. I wonder if we can go more towards react query architecture where dependencies resolve and fetches will happen if there is a read. That should be an abstraction as that's the point of a state/data cache library. |
@talksik I'm not sure what this is about. Providers also resolve/fetch on read. |
Describe the bug
In the following code, future returns error with
Bad state: The provider AutoDisposeFutureProvider<String>#25550(example) was disposed before a value was emitted.
.v0.14.0+3 works fine.
To Reproduce
Expected behavior
Show 'example' in ListTile.
The text was updated successfully, but these errors were encountered: