Skip to content
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

Assert throws: '_builtFuture == null': Bad state #1920

Closed
timcreatedit opened this issue Nov 17, 2022 · 12 comments · Fixed by #2025
Closed

Assert throws: '_builtFuture == null': Bad state #1920

timcreatedit opened this issue Nov 17, 2022 · 12 comments · Fixed by #2025
Labels
bug Something isn't working needs triage

Comments

@timcreatedit
Copy link
Sponsor

timcreatedit commented Nov 17, 2022

Describe the bug
FutureProvider throws AssertionError on hooks_riverpod 2.1.1:
_AssertionError ('package:riverpod/src/async_notifier/base.dart': Failed assertion: line 219 pos 12: '_builtFuture == null': Bad state)
Stacktrace:

_AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:51)
_AssertionError._throwNew (dart:core-patch/errors_patch.dart:40)
FutureHandlerProviderElementMixin.handleFuture (/Users/tim/flutter/.pub-cache/hosted/pub.dartlang.org/riverpod-2.1.1/lib/src/async_notifier/base.dart:219)
FutureProviderElement.create (/Users/tim/flutter/.pub-cache/hosted/pub.dartlang.org/riverpod-2.1.1/lib/src/future_provider/base.dart:93)
ProviderElementBase.buildState (/Users/tim/flutter/.pub-cache/hosted/pub.dartlang.org/riverpod-2.1.1/lib/src/framework/element.dart:421)
ProviderElementBase._performBuild (/Users/tim/flutter/.pub-cache/hosted/pub.dartlang.org/riverpod-2.1.1/lib/src/framework/element.dart:359)
ProviderElementBase.flush (/Users/tim/flutter/.pub-cache/hosted/pub.dartlang.org/riverpod-2.1.1/lib/src/framework/element.dart:322)
ProviderElementProxy.read (/Users/tim/flutter/.pub-cache/hosted/pub.dartlang.org/riverpod-2.1.1/lib/src/framework/proxy_provider_listenable.dart:115)
ProviderElementProxy.addListener.<anonymous closure> (/Users/tim/flutter/.pub-cache/hosted/pub.dartlang.org/riverpod-2.1.1/lib/src/framework/proxy_provider_listenable.dart:101)
_ProxySubscription.read (/Users/tim/flutter/.pub-cache/hosted/pub.dartlang.org/riverpod-2.1.1/lib/src/framework/proxy_provider_listenable.dart:21)
ProviderElementBase.watch (/Users/tim/flutter/.pub-cache/hosted/pub.dartlang.org/riverpod-2.1.1/lib/src/framework/element.dart:701)
therapistImageProvider.<anonymous closure> (/Users/tim/Developer/fyzo/patient/lib/services/provider/therapist.provider.dart:32)
AutoDisposeFutureProvider._create (/Users/tim/flutter/.pub-cache/hosted/pub.dartlang.org/riverpod-2.1.1/lib/src/future_provider/auto_dispose.dart:29)
FutureProviderElement.create.<anonymous closure> (/Users/tim/flutter/.pub-cache/hosted/pub.dartlang.org/riverpod-2.1.1/lib/src/future_provider/base.dart:94)
Result.guard (/Users/tim/flutter/.pub-cache/hosted/pub.dartlang.org/riverpod-2.1.1/lib/src/result.dart:18)
FutureHandlerProviderElementMixin.handleFuture (/Users/tim/flutter/.pub-cache/hosted/pub.dartlang.org/riverpod-2.1.1/lib/src/async_notifier/base.dart:223)
FutureProviderElement.create (/Users/tim/flutter/.pub-cache/hosted/pub.dartlang.org/riverpod-2.1.1/lib/src/future_provider/base.dart:93)
ProviderElementBase.buildState (/Users/tim/flutter/.pub-cache/hosted/pub.dartlang.org/riverpod-2.1.1/lib/src/framework/element.dart:421)
ProviderElementBase._performBuild (/Users/tim/flutter/.pub-cache/hosted/pub.dartlang.org/riverpod-2.1.1/lib/src/framework/element.dart:359)
ProviderElementBase.flush (/Users/tim/flutter/.pub-cache/hosted/pub.dartlang.org/riverpod-2.1.1/lib/src/framework/element.dart:322)

To Reproduce
The problem happens, when one of our FutureProvider.autoDispose tries to watch another FutureProvider.autoDispose. When exactly, I can't really tell, it seems to be timing related, but I don't understand enough about the error yet.

For reference, these are the providers in question:

final therapistPersonProvider =
    FutureProvider.autoDispose<Person?>((ref) async {
  final plan = await ref.watch(planProvider.future);
  final link = await ref.watch(currentClinicLinkProvider.future);

  final lastTherapistUid = plan?.therapistUids.lastOrNull;
  if (lastTherapistUid != null) {
    return link?.therapists
            .firstWhereOrNull((t) => t.therapistUid == lastTherapistUid)
            ?.person ??
        plan?.therapistData;
  }
  return plan?.therapistData;
});

...

final therapistImageProvider =
    FutureProvider.autoDispose<ImageProvider?>((ref) async {
  final person = await ref.watch(therapistPersonProvider.future); // ERROR HAPPENS HERE
  final path = person?.profilePicturePath;
  if (path == null || path.isEmpty) {
    return null;
  }
  //TODO return CachedImageNetworkProvider
  return null;
});
@timcreatedit timcreatedit added bug Something isn't working needs triage labels Nov 17, 2022
@rrousselGit
Copy link
Owner

Could you create a sample that can be executed?

.future works for me.

@timcreatedit
Copy link
Sponsor Author

It's a bit difficult for me to pin down the problem for a minimum example. We're using .future all over the App, and it only breaks in this one place so far. It doesn't seem to matter if the Providers autoDispose or not. I'll see what I can find

@rrousselGit
Copy link
Owner

Thanks! Otherwise it's a bit hard for me to pin down how it happened too.
To begin with, this assert is supposed to never be reached

@timcreatedit
Copy link
Sponsor Author

timcreatedit commented Nov 17, 2022

Thanks! Otherwise it's a bit hard for me to pin down how it happened too.

Always happy to break new ground 😅

I feel like I've stumbled upon a mistake on our side that might have something to do with the issue.
I've read here that we seem to be using FutureProviders wrong in some cases. Therefore I have a question (that might be slightly unrelated to this issue now, but I couldn't find documentation on it anywhere):

What's the intended way of writing FutureProvider's that depend on the results of multiple other FutureProviders then?

final cProvider = FutureProvider((ref) async {
    final a = await ref.watch(aProvider.future);
    final b = await ref.watch(bProvider.future);
    // or even:
    final d = await ref.watch(dProvider(a).future);
});

To my understanding, this isn't recommended because the Provider could be rebuilt due to changes to a, while b or d are still awaiting?

Thank you so much for your help

@rrousselGit
Copy link
Owner

It's mainly about autoDispose. It could be a problem

But I doubt that's related to your issue.

@timcreatedit
Copy link
Sponsor Author

I can't seem to find the issue and we're in a tight spot right now, so I'll have to revert to 2.0.0-dev.9 for now, everything works fine there. I'll get back to you when we have more time again but thank you for your help

@Zekfad
Copy link

Zekfad commented Dec 10, 2022

Facing the same error (version 2.1.1).
No reproduction for right now, can't find the root cause.
From what we see, it looks like the problem occurs when you use data twice from the same Auto Dispose Future Provider (AsyncNotifier in our case actually).
We have Auto Dispose Async Notifier exposing int and use it in one place, and there's FutureProvider.autoDispose that use this provider's .future additionally.

@Zekfad
Copy link

Zekfad commented Dec 10, 2022

@rrousselGit found workaround for this one: wrapping one of ref.watch(provider(arg).future) with Future.microtask works around this issue.

@mgwrd
Copy link

mgwrd commented Dec 18, 2022

I have created a sample that can be executed: https://github.com/mgwrd/riverpod-error

If you try to watch the combined provider in main.dart the error occurs. If you change line 22 to the "non-combined" provider, the error does not occur.

@rrousselGit
Copy link
Owner

Thank y'all. I'll admit I'm doing other things at the moment. If someone feels fancy fixing it that'd be sweet. Otherwise it'll probably be delayed by a few weeks

@mgwrd
Copy link

mgwrd commented Dec 23, 2022

I tinkered a bit with the code and it looks like using update() inside of one of two "chained" AsyncNotifiers throws this error. I have updated the sample: https://github.com/mgwrd/riverpod-error

The relevant code:

@riverpod
class ExampleNotifier extends _$ExampleNotifier {
  @override
  FutureOr<List<int>> build() async {
    final a = await ref.watch(streamAProvider.future);
    //final b = await ref.watch(streamBProvider.future);
    return [a, a];
  }

  Future<void> updateStreamsWithoutError() async {
    state = const AsyncLoading();
    final a = Random().nextInt(20);
    await Future.delayed(const Duration(milliseconds: 2000));
    aSubject.add(a);
  }

  Future<void> updateStreamsWithError() async {
    // when uncommenting the next line, the state is in an infinite loading state and does not update
    // state = const AsyncLoading();
    update((state) async {
      final a = Random().nextInt(20);
      await Future.delayed(const Duration(milliseconds: 2000));
      aSubject.add(a);
      return [a];
    });
  }

}

However, I'm not sure if this is what caused the error for @timcreatedit. I still get the error in my app without using update(), but I can't reproduce it in a simple example. I suspect it has something to do with BehaviourSubject from rxdart - at some point i got the error Bad state: Too many elements before getting _builtFuture == null. Let's hope it's the same, just triggered differently.

@rrousselGit rrousselGit linked a pull request Dec 24, 2022 that will close this issue
@rrousselGit
Copy link
Owner

This should be indirectly fixed by the next release. Since there's no more _builtFuture variable :p

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working needs triage
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants