I have created a parent provider which is dependent on multiple providers.
@riverpod
class ParentHeader extends _$ParentHeader {
@override
FutureOr<bool> build(HeaderViewModel viewModel) async {
return await parentHeader(viewModel);
}
Future<bool> parentHeader(HeaderViewModel headerVM) async {
Trial? trial =
await ref.watch(trialProvider.future);
if (trial != null) {
Future tasks =
ref.watch(tasksProvider(headerVM.strStartDate, headerVM.strEndDate).future);
Future video = ref
.watch(videoProvider(headerVM.tmpStartDate, headerVM.tmpEndDate).future);
Future calendar = ref.watch(
calendarProvider(headerVM.strStartDate, headerVM.strEndDate).future);
var listOfResponses =
await Future.wait([tasks, calendar, video]);
return Future.value(true);
}
}
On an event, I want to invalidate only the calendarProvider
. When I try to do so all the Future.wait
3 providers(API's) get called again. But the trialProvider
is not called.
Example of a provider I am using
@riverpod
class Calendar extends _$Calendar {
@override
FutureOr<List<CalendarModel>> build(String startDate, String endDate) async {
List<CalendarModel> model = await calendarEvents(startDate, endDate);
return model;
}
Future<List<CalendarModel>> calendarEvents(String startDate, String endDate) async {
EndPoint endPoint = EndPoint();
endPoint.parameters = {"from": startDate, "to": endDate};
try {
NetworkResponse? response = await _Api().fetch(endPoint);
return response.when(success: (response) {
List<CalendarModel> events = [];
var responseMessage =
CalendarModelResponse.fromJson(response.data);
events = responseMessage.message;
return Future.value(events);
},
error: (error) {
debugPrint(" error - ${error.message}");
});
}
}
}
I am not sure what I am doing wrong. Or is there any other way to achieve this?
Calling await ref.watch(trialProvider.future);
is adding an async gap. During that gap Riverpod is disposing the tasks, video, and calendar providers because they don't have any subscribers. After the async gap completes and each of those providers are passed to ref.watch
, Riverpod recreates them and reruns the API calls or whatever other logic you have written.
The simplest way around this is to execute all your .watch
code at the start of parentHeader
before any async gaps:
Future<bool> parentHeader(HeaderViewModel headerVM) async {
final tasks = ref.watch(
tasksProvider(headerVM.strStartDate, headerVM.strEndDate).future,
);
final video = ref.watch(
videoProvider(headerVM.strStartDate, headerVM.strEndDate).future,
);
final calendar = ref.watch(
calendarProvider(headerVM.strStartDate, headerVM.strEndDate).future,
);
final trial = await ref.watch(trialProvider.future);
if (trial != null) {
var listOfResponses = await Future.wait([tasks, calendar, video]);
return true;
}
// Other logic here
return false;
}
This will prevent the providers from disposing but changes the provider behavior slightly: parentHeaderProvider
will now always depend on tasksProvider
, videoProvider
, and calendarProvider
, even if trialProvider
returns null
. This means those providers will be kept alive as long as parentHeaderProvider
is, and any one of them updating will cause parentHeaderProvider
to rebuild. Depending on your application maybe that's fine, and Riverpod's caching logic means it shouldn't incur too much overhead.
If you do need to avoid unnecessarily listening to those providers, you can (ab)use ref.listen
to temporarily keep alive only already initialized providers during the async gap:
Future<bool> parentHeader(HeaderViewModel headerVM) async {
// Listen to all potential dependencies that are already initialized to
// ensure they are not disposed during an async gap.
final tProvider = tasksProvider(headerVM.strStartDate, headerVM.strEndDate);
final tSub = _listenIfInitialized(ref, tProvider);
final vProvider = videoProvider(headerVM.strStartDate, headerVM.strEndDate);
final vSub = _listenIfInitialized(ref, vProvider);
final cProvider = calendarProvider(headerVM.strStartDate, headerVM.strEndDate);
final cSub = _listenIfInitialized(ref, cProvider);
// Enter async gap.
final trial = await ref.watch(trialProvider.future);
// Past async gap, close any listeners we created.
tSub?.close();
vSub?.close();
cSub?.close();
if (trial != null) {
final tasks = ref.watch(tProvider.future);
final video = ref.watch(vProvider.future);
final calendar = ref.watch(cProvider.future);
var listOfResponses = await Future.wait([tasks, calendar, video]);
return true;
}
// Other logic here
return false;
}
ProviderSubscription? _listenIfInitialized(Ref ref, ProviderBase provider) {
if (!ref.exists(provider)) return null;
final handle = ref.listen(provider, (_, _) {});
return handle;
}