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

ref.exists() returns true when manually invalidate a provider. #3558

Open
hasanmhallak opened this issue May 20, 2024 · 0 comments
Open

ref.exists() returns true when manually invalidate a provider. #3558

hasanmhallak opened this issue May 20, 2024 · 0 comments
Assignees
Labels
bug Something isn't working needs triage

Comments

@hasanmhallak
Copy link

Describe the bug
It appears that ref.exist(someProvider) still returns true when manually invalidate a provider, and when try to use the invalidated provider onResume gets called. While when we set keepAlive to true or use AutoDisposeNotifier everything works as expected.

This behavior is found when using / not using generated code.

Original issue #2729

To Reproduce

Code Generation Sample
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'main.g.dart';

@Riverpod(keepAlive: true)
int fetchItem(FetchItemRef ref) {
  // this will be called on invalidate.
  ref.onDispose(() => print('dispose'));

  ref.onCancel(() => print('onCancel'));

  // but this will be called when we use
  // the provider after invalidating
  ref.onResume(() => print('onResume'));
  return 5;
}

void main() {
  runApp(
    const ProviderScope(child: MaterialApp(home: MyHomePage())),
  );
}

class MyHomePage extends ConsumerWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(onPressed: () {
        print('does exist: ${ref.exists(fetchItemProvider)}');
      }),
      body: Center(
        child: TextButton(
          onPressed: () {
            print('Navigate To Next Page');
            Navigator.of(context).push(MaterialPageRoute(builder: (_) => MyHomePage2()));
          },
          child: Text('Navigate To Next Page'),
        ),
      ),
    );
  }
}

class MyHomePage2 extends ConsumerStatefulWidget {
  const MyHomePage2({super.key});

  @override
  ConsumerState<MyHomePage2> createState() => _MyHomePage2State();
}

class _MyHomePage2State extends ConsumerState<MyHomePage2> {
  ProviderContainer? _container;

  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    _container!.invalidate(fetchItemProvider);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    _container = ProviderScope.containerOf(context);
    return Scaffold(
      appBar: AppBar(),
      floatingActionButton: FloatingActionButton(onPressed: () {
        print('does exist: ${ref.exists(fetchItemProvider)}');
      }),
      body: Center(
        child: Text(ref.watch(fetchItemProvider).toString()),
      ),
    );
  }
}
No Code Generation Sample with explanation

To Reproduce

  1. run the code
  2. Click Exist Fab
  3. Navigate to next page
  4. Click the + Fab to increment state
  5. Click Exist
  6. Navigate Back
  7. Click Exist

Logs

flutter: does exist: false
flutter: internal State: 5
flutter: Navigate To Next Page
flutter: does exist: true
flutter: internal State: 7
flutter: dispose
flutter: does exist: true // the instance still exists and the internal state is preserved.
flutter: internal State: 7
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class FetchItemsNotifier extends Notifier<int> {
  int internalState = 5;
  @override
  build() {
    // this will be called on invalidate.
    ref.onDispose(() => print('dispose'));

    ref.onCancel(() => print('onCancel'));

    // but this will be called when we use
    // the provider after invalidating
    ref.onResume(() => print('onResume'));
    return 5;
  }

  void add() {
    state = state + 1;
    internalState = state;
  }
}

final fetchItemProvider = NotifierProvider<FetchItemsNotifier, int>(FetchItemsNotifier.new);

void main() {
  runApp(
    const ProviderScope(child: MaterialApp(home: MyHomePage())),
  );
}

class MyHomePage extends ConsumerWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: Text('Exist'),
        onPressed: () {
          print('does exist: ${ref.exists(fetchItemProvider)}');
          print('internal State: ${ref.read(fetchItemProvider.notifier).internalState}');
        },
      ),
      body: Center(
        child: TextButton(
          onPressed: () {
            print('Navigate To Next Page');
            Navigator.of(context).push(MaterialPageRoute(builder: (_) => MyHomePage2()));
          },
          child: Text('Navigate To Next Page'),
        ),
      ),
    );
  }
}

class MyHomePage2 extends ConsumerStatefulWidget {
  const MyHomePage2({super.key});

  @override
  ConsumerState<MyHomePage2> createState() => _MyHomePage2State();
}

class _MyHomePage2State extends ConsumerState<MyHomePage2> {
  ProviderContainer? _container;

  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    _container!.invalidate(fetchItemProvider);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    _container = ProviderScope.containerOf(context);
    return Scaffold(
      appBar: AppBar(),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            heroTag: 'Exist',
            child: Text('Exist'),
            onPressed: () {
              print('does exist: ${ref.exists(fetchItemProvider)}');
              print('internal State: ${ref.read(fetchItemProvider.notifier).internalState}');
            },
          ),
          FloatingActionButton(
            child: Text('+'),
            onPressed: () {
              ref.read(fetchItemProvider.notifier).add();
            },
          ),
        ],
      ),
      body: Center(
        child: Text(ref.watch(fetchItemProvider).toString()),
      ),
    );
  }
}

When we switch to using AutoDisposeNotifier the internal state is resets (which means the old provider instance are removed from the container) and ref.exists() return false. which will produce the following logs:

**Logs When Using AutoDisposeNotifier **

flutter: does exist: false
flutter: internal State: 5
flutter: dispose // got disposed after accessing the internal state of the provider when no active listener registered.
flutter: Navigate To Next Page
flutter: does exist: true
flutter: internal State: 8
flutter: dispose 
flutter: does exist: false // auto dispose works as expected.
flutter: internal State: 5 // a new AutoDisposeNotifier instance created
flutter: dispose // got disposed after accessing the internal state of the provider when no active listener registered.
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

No branches or pull requests

2 participants