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

Suggestions for improving documentation on keepAlive #3880

Open
koji-1009 opened this issue Dec 21, 2024 · 2 comments
Open

Suggestions for improving documentation on keepAlive #3880

koji-1009 opened this issue Dec 21, 2024 · 2 comments
Assignees
Labels
documentation Improvements or additions to documentation needs triage

Comments

@koji-1009
Copy link

koji-1009 commented Dec 21, 2024

Describe what scenario you think is uncovered by the existing examples/articles

link: https://riverpod.dev/docs/essentials/auto_dispose#fine-tuned-disposal-with-refkeepalive

If keepAlive is implemented as described, it may not be cached as expected when caching with ref.read.

Implemented according to the documentation, the code is as follows.

@riverpod
Future<String> readAndFailedKeepAlive(Ref ref) async {
  print('readAndFailedKeepAlive:Provider: Started');
  ref
    ..onCancel(() {
      print('readAndFailedKeepAlive:Provider: Canceled');
    })
    ..onResume(() {
      print('readAndFailedKeepAlive:Provider: Resumed');
    })
    ..onDispose(() {
      print('readAndFailedKeepAlive:Provider: Disposed');
    });

  final result = await computeValue('Read and failed keep alive');
  print('readAndFailedKeepAlive:Provider: Get result');

  ref.keepAlive();

  print('readAndFailedKeepAlive:Provider: Finished');
  return result;
}

Calling it with ref.read will result in the following output.

flutter: readAndFailedKeepAlive:Provider: Started
flutter: readAndFailedKeepAlive:Provider: Disposed
flutter: readAndFailedKeepAlive:Provider: Get result
flutter: readAndFailedKeepAlive:Provider: Finished

Describe why existing examples/articles do not cover this case

This problem can be solved by doing keepAlive before the await process, as in the cacheFor extension.

https://riverpod.dev/docs/essentials/auto_dispose#example-keeping-state-alive-for-a-specific-amount-of-time

@riverpod
Future<String> readAndKeepAlive(Ref ref) async {
  print('readAndKeepAlive:Provider: Started');
  ref
    ..onCancel(() {
      print('readAndKeepAlive:Provider: Canceled');
    })
    ..onResume(() {
      print('readAndKeepAlive:Provider: Resumed');
    })
    ..onDispose(() {
      print('readAndKeepAlive:Provider: Disposed');
    });

  final link = ref.keepAlive();
  final String result;
  try {
    result = await computeValue('Read and keep alive');
    print('readAndKeepAlive:Provider: Get result');
  } on Exception catch (_) {
    link.close();
    rethrow;
  }

  print('readAndKeepAlive:Provider: Finished');
  return result;
}

Calling it with ref.read will result in the following output.

flutter: readAndKeepAlive:Provider: Started
flutter: readAndKeepAlive:Provider: Get result
flutter: readAndKeepAlive:Provider: Finished

Additional context

I understand that this behavior occurs when a Provider that is not ref.watch and not keepAlive exists across frames. For this reason, I would be happy to improve the documentation, not the implementation.

example: https://github.com/koji-1009/riverpod_keepalive_example

@koji-1009 koji-1009 added documentation Improvements or additions to documentation needs triage labels Dec 21, 2024
@rrousselGit
Copy link
Owner

The behavior of the example is intended. If the provider can be disposed before the request completed, that likely makes more sense.
In that scenario, you should cancel your HTTP request. This saves ressources

@koji-1009
Copy link
Author

Yes, this behavior is as expected. This is not a bug report submission.

It is important to note that if keepAlive is set after a Ref is disposed, it will not be reflected. If we use ref.read, the Provider will be disposed during the computeValue. Therefore, if we set keepAlive after computeValue, it will not be reflected.

The following log is output to the Provider that invoked keepAlive after computeValue with ref.read. (tapped button 3 times) The calculation results are not cached.

flutter: readAndFailedKeepAlive:Button: started
flutter: readAndFailedKeepAlive:Provider: Started
flutter: computeValue: Started
flutter: readAndFailedKeepAlive:Provider: Disposed
flutter: computeValue: Finished
flutter: readAndFailedKeepAlive:Provider: Get result
flutter: readAndFailedKeepAlive:Provider: Finished
flutter: readAndFailedKeepAlive:Button: finished: Read and failed keep alive
flutter: readAndFailedKeepAlive:Button: started
flutter: readAndFailedKeepAlive:Provider: Started
flutter: computeValue: Started
flutter: readAndFailedKeepAlive:Provider: Disposed
flutter: computeValue: Finished
flutter: readAndFailedKeepAlive:Provider: Get result
flutter: readAndFailedKeepAlive:Provider: Finished
flutter: readAndFailedKeepAlive:Button: finished: Read and failed keep alive
flutter: readAndFailedKeepAlive:Button: started
flutter: readAndFailedKeepAlive:Provider: Started
flutter: computeValue: Started
flutter: readAndFailedKeepAlive:Provider: Disposed
flutter: computeValue: Finished
flutter: readAndFailedKeepAlive:Provider: Get result
flutter: readAndFailedKeepAlive:Provider: Finished
flutter: readAndFailedKeepAlive:Button: finished: Read and failed keep alive

If keepAlive is called before computeValue, the result of the operation is cached.

flutter: readAndKeepAlive:Button: started
flutter: readAndKeepAlive:Provider: Started
flutter: computeValue: Started
flutter: computeValue: Finished
flutter: readAndKeepAlive:Provider: Get result
flutter: readAndKeepAlive:Provider: Finished
flutter: readAndKeepAlive:Button: finished: Read and keep alive
flutter: readAndKeepAlive:Button: started
flutter: readAndKeepAlive:Button: finished: Read and keep alive
flutter: readAndKeepAlive:Button: started
flutter: readAndKeepAlive:Button: finished: Read and keep alive
Sample application and providers
class MyPage extends ConsumerWidget {
  const MyPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Example keepAlive'),
      ),
      body: Center(
        child: Column(
          spacing: 16,
          children: [
            FilledButton(
              onPressed: () async {
                print('readAndKeepAlive:Button: started');
                final value = await ref.read(
                  readAndKeepAliveProvider.future,
                );
                print('readAndKeepAlive:Button: finished: $value');
              },
              child: const Text('Read readAndKeepAlive'),
            ),
            FilledButton.tonal(
              onPressed: () async {
                print('readAndFailedKeepAlive:Button: started');
                final value = await ref.read(
                  readAndFailedKeepAliveProvider.future,
                );
                print('readAndFailedKeepAlive:Button: finished: $value');
              },
              child: const Text('Read readAndFailedKeepAlive'),
            ),
          ],
        ),
      ),
    );
  }
}

const keepDuration = Duration(
  seconds: 10,
);

Future<String> computeValue(String value) async {
  print('computeValue: Started');
  await Future.delayed(
    const Duration(
      seconds: 5,
    ),
  );
  print('computeValue: Finished');
  return value;
}

@riverpod
Future<String> readAndKeepAlive(Ref ref) async {
  print('readAndKeepAlive:Provider: Started');
  ref
    ..onCancel(() {
      print('readAndKeepAlive:Provider: Canceled');
    })
    ..onResume(() {
      print('readAndKeepAlive:Provider: Resumed');
    })
    ..onDispose(() {
      print('readAndKeepAlive:Provider: Disposed');
    });

  final link = ref.keepAlive();
  final String result;
  try {
    result = await computeValue('Read and keep alive');
    print('readAndKeepAlive:Provider: Get result');
  } on Exception catch (_) {
    link.close();
    rethrow;
  }

  print('readAndKeepAlive:Provider: Finished');
  return result;
}

@riverpod
Future<String> readAndFailedKeepAlive(Ref ref) async {
  print('readAndFailedKeepAlive:Provider: Started');
  ref
    ..onCancel(() {
      print('readAndFailedKeepAlive:Provider: Canceled');
    })
    ..onResume(() {
      print('readAndFailedKeepAlive:Provider: Resumed');
    })
    ..onDispose(() {
      print('readAndFailedKeepAlive:Provider: Disposed');
    });

  final result = await computeValue('Read and failed keep alive');
  print('readAndFailedKeepAlive:Provider: Get result');

  ref.keepAlive();

  print('readAndFailedKeepAlive:Provider: Finished');
  return result;
}  

Again, since Riverpod is centered on ref.watch, I believe this behavior is as expected. However, users (myself included) sometimes try to keepAlive with ref.read. Maybe, this issue raised the same question.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation needs triage
Projects
None yet
Development

No branches or pull requests

2 participants