Chema
Chema

Reputation: 745

obtain response after making request in user interaction

When a button is pressed, I'm displaying a message in the SnackBar when sending the request. I'd like to also display the response once it arrives. I've played for a while, and I expected ref.read(insertFilesProvider).requireValue to work, but it fails with:

Tried to call `requireValue` on an `AsyncValue` that has no value.

Which produces no Gugl hits. Couldn't figure out how to preload the provider's AsyncValue cache (the build method returns an empty string, shouldn't that initialize it? Or does requireValue only work with ref.watch/listen?), so I've resorted to using ref.listen() in the widget's build() (with the microtask hack) as a workaround. Is there a way to do it after the request in onPressed?

Here's the (simplified) button widget:

class InsertFilesButton extends HookConsumerWidget {
  const InsertFilesButton({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Listen for changes to show server response
    // TODO: make it work onPressed
    // Hack: wait for next frame with microtask() for code to actually execute
    ref.listen(
        insertFilesProvider,
        (prev, now) => Future.microtask(() {
              switch (now) {
                case AsyncData(value: final data):
                  SnackBarUtils.showSnackBar(context, data);
              }
            }));

    return FilledButton(
      onPressed: () async {
        // Get selectedFiles from its provider
        final selectedFiles = ref.read(selectedInsertFilesProvider);
        SnackBarUtils.showSnackBar(context, "Enqueuing jobs on server...");
        ref.read(insertFilesProvider.notifier).process(selectedFiles);
        // final result = ref.read(insertFilesProvider);
        // log.d(result);
        // Error: Unhandled Exception: Bad state: Tried to call `requireValue` on an `AsyncValue` that has no value
        // SnackBarUtils.showSnackBar(context, result.requireValue);
      },
      child: const Text("Insert Files"),
    );
  }
}

And the provider:

@riverpod
class InsertFiles extends _$InsertFiles {
  @override
  Future<String> build() async => '';

  Future<void> process(
    List files,
  ) async {
    final bodyData = jsonEncode({'filenames': files});

    final response = await dio.post(
      '$api/insert/',
      options: Options(headers: {
        HttpHeaders.contentTypeHeader: "application/json",
      }),
      data: jsonEncode(bodyData),
    );
    final parsedResponse = InsertFilesResponse.fromJson(response.data);
    final result = parsedResponse.message;

    state = AsyncData(result);
  }
}

@freezed
class InsertFilesResponse with _$InsertFilesResponse {
  factory InsertFilesResponse({required String message}) = _InsertFilesResponse;

  factory InsertFilesResponse.fromJson(Map<String, Object?> json) =>
      _$InsertFilesResponseFromJson(json);
}

Upvotes: 0

Views: 60

Answers (1)

Krisna Pranata
Krisna Pranata

Reputation: 111

the usage of 'ref.listen' is correct, but there a few things that need to be adjusted as shown in the example below

ref.listen(insertFilesProvider, (prev, next) {
  if (prev != next) {
    if (next.hasValue) {
       final data = next.value;
       SnackBarUtils.showSnackBar(context, data)
    }
  }
});
  1. Check if prev and next are not the same to prevent the code from running more than once with the same value.
  2. Check whether the current value exists; if it does, a snackbar is displayed.

Upvotes: 0

Related Questions