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

selectAsync does not update immediately after invalidate #3959

Open
ksatoudeveloper opened this issue Feb 3, 2025 · 2 comments
Open

selectAsync does not update immediately after invalidate #3959

ksatoudeveloper opened this issue Feb 3, 2025 · 2 comments
Assignees
Labels
bug Something isn't working with-repro

Comments

@ksatoudeveloper
Copy link

Describe the bug
I encountered an issue when using selectAsync with invalidate().
The provider using watch updates as expected, but the provider using selectAsync does not update immediately after invalidate().

  • When using refresh() instead of invalidate(), the provider updates as expected.
  • When add Future.delayed after invalidate(), the provider updates as expected.
  • When not using selectAsync, the provider updates as expected.

To Reproduce

import 'dart:math';

import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:test/test.dart';
part 'riv_test.g.dart';

@Riverpod(keepAlive: true)
Random random(Ref ref) {
  return Random(1);
}

@Riverpod(keepAlive: true)
Future<({int h, int r})> cone(Ref ref) async {
  final random = ref.watch(randomProvider);
  final cone = (
    h: random.nextInt(10),
    r: random.nextInt(10)
  ); // (h: 4, r: 7), (h: 5, r: 9), ...
  return cone;
}

@Riverpod(keepAlive: true)
Future<double> baseAreaSelectAsync(Ref ref) async {
  final r = await ref.watch(coneProvider.selectAsync((c) => c.r));
  return r * r * pi;
}

@Riverpod(keepAlive: true)
Future<double> baseArea(Ref ref) async {
  final (h: _, r: r) = await ref.watch(coneProvider.future);
  return r * r * pi;
}

void main() {
  late ProviderContainer container;
  setUp(() {
    container = ProviderContainer();
  });
  test('watch-selectAsync invalidate', () async {
    await expectLater(
      container.read(baseAreaSelectAsyncProvider.future),
      completion(153.93804002589985),
    );
    container.invalidate(coneProvider);
    await expectLater(
      container.read(baseAreaSelectAsyncProvider.future),
      completion(254.46900494077323), // Failed actual 153.93804002589985
    );
  });
  test('watch-selectAsync refresh', () async {
    await expectLater(
      container.read(baseAreaSelectAsyncProvider.future),
      completion(153.93804002589985),
    );
    await container.refresh(coneProvider.future);
    await expectLater(
      container.read(baseAreaSelectAsyncProvider.future),
      completion(254.46900494077323),
    );
  });
  test('watch-selectAsync invalidate delay', () async {
    await expectLater(
      container.read(baseAreaSelectAsyncProvider.future),
      completion(153.93804002589985),
    );
    container.invalidate(coneProvider);
    await Future.delayed(Duration.zero);
    await expectLater(
      container.read(baseAreaSelectAsyncProvider.future),
      completion(254.46900494077323),
    );
  });
  test('watch invalidate', () async {
    await expectLater(
      container.read(baseAreaProvider.future),
      completion(153.93804002589985),
    );
    container.invalidate(coneProvider);
    await expectLater(
      container.read(baseAreaProvider.future),
      completion(254.46900494077323),
    );
  });
  test('watch refresh', () async {
    await expectLater(
      container.read(baseAreaProvider.future),
      completion(153.93804002589985),
    );
    await container.refresh(coneProvider.future);
    await expectLater(
      container.read(baseAreaProvider.future),
      completion(254.46900494077323),
    );
  });
  tearDown(() {
    container.dispose();
  });
}

Expected behavior
When coneProvider is invalidated, calling container.read(baseAreaSelectAsyncProvider.future) should immediately return the updated value.

@rrousselGit
Copy link
Owner

Considering refresh is quite literally:

T refresh<T>(provider) {
  invalidate(provider);
  return read(provider);
}

Then there's something else going on.

But thanks for taking the time to write tests! I'll look into it :)

@lucavenir
Copy link
Collaborator

lucavenir commented Feb 25, 2025

Glad to see more folks are writing tests directly in their issues 😄

I reproduced the whole case and added the corresponding label; I also want to add this failing test to the previous suite:

test('watch-selectAsync invalidate microtask', () async {
  await expectLater(
    container.read(baseAreaSelectAsyncProvider.future),
    completion(153.93804002589985),
  );
  container.invalidate(coneProvider);
  await Future.microtask(() {}); // instead of a delay
  await expectLater(
    container.read(baseAreaSelectAsyncProvider.future),
    completion(254.46900494077323),  // still 153.93804002589985
  );
});

Finally, for convenience, here are all the tests clustered together, with no codegen involved.

Full Repro
import 'dart:math';

import 'package:riverpod/riverpod.dart';
import 'package:test/test.dart';

final randomProvider = Provider<Random>((ref) {
  return Random(1);
});

final coneProvider = FutureProvider<({int h, int r})>((ref) async {
  final random = ref.watch(randomProvider);
  final cone = (
    h: random.nextInt(10),
    r: random.nextInt(10)
  ); // (h: 4, r: 7), (h: 5, r: 9), ...
  return cone;
});

final baseAreaSelectAsyncProvider = FutureProvider<double>((ref) async {
  final r = await ref.watch(coneProvider.selectAsync((c) => c.r));
  return r * r * pi;
});

final baseAreaProvider = FutureProvider<double>((ref) async {
  final (h: _, r: r) = await ref.watch(coneProvider.future);
  return r * r * pi;
});

void main() {
  late ProviderContainer container;
  setUp(() {
    container = ProviderContainer();
  });
  test('watch-selectAsync invalidate', () async {
    await expectLater(
      container.read(baseAreaSelectAsyncProvider.future),
      completion(153.93804002589985),
    );
    container.invalidate(coneProvider);
    await expectLater(
      container.read(baseAreaSelectAsyncProvider.future),
      completion(254.46900494077323), // Failed, actual 153.93804002589985
    );
  });
  test('watch-selectAsync refresh', () async {
    await expectLater(
      container.read(baseAreaSelectAsyncProvider.future),
      completion(153.93804002589985),
    );
    await container.refresh(coneProvider.future);
    await expectLater(
      container.read(baseAreaSelectAsyncProvider.future),
      completion(254.46900494077323),
    );
  });
  test('watch-selectAsync invalidate delay', () async {
    await expectLater(
      container.read(baseAreaSelectAsyncProvider.future),
      completion(153.93804002589985),
    );
    container.invalidate(coneProvider);
    await Future.delayed(Duration.zero);
    await expectLater(
      container.read(baseAreaSelectAsyncProvider.future),
      completion(254.46900494077323),
    );
  });
  test('watch-selectAsync invalidate microtask', () async {
    await expectLater(
      container.read(baseAreaSelectAsyncProvider.future),
      completion(153.93804002589985),
    );
    container.invalidate(coneProvider);
    await Future.microtask(() {}); // instead of a delay
    await expectLater(
      container.read(baseAreaSelectAsyncProvider.future),
      completion(254.46900494077323), // still 153.93804002589985
    );
  });
  test('watch invalidate', () async {
    await expectLater(
      container.read(baseAreaProvider.future),
      completion(153.93804002589985),
    );
    container.invalidate(coneProvider);
    await expectLater(
      container.read(baseAreaProvider.future),
      completion(254.46900494077323),
    );
  });
  test('watch refresh', () async {
    await expectLater(
      container.read(baseAreaProvider.future),
      completion(153.93804002589985),
    );
    await container.refresh(coneProvider.future);
    await expectLater(
      container.read(baseAreaProvider.future),
      completion(254.46900494077323),
    );
  });
  tearDown(() {
    container.dispose();
  });
}

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

No branches or pull requests

3 participants