Reputation: 745
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
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)
}
}
});
Upvotes: 0