I’m using FutureProvider in Riverpod for Flutter to return an API call with data and update the UI. While its loading it shows the CircularIndicator, then when loaded it displays the data on the home page. This is working the first time.
However, at a later stage in the app, the user triggers the call again, and it updates the UI on the home page. This is working, and I refresh the provider from the other screen, and the UI of the main screen updates. However, it doesn’t show the loading indicator, it just updates automatically after a few seconds when it fetchs the result.
Why isn’t the loading() state being called the second time.
Here are my 2 providers:
final getCalendarApiProvider = Provider<GetCalendarApi>(
(ref) => GetCalendarApi(),
);
final calendarResponseProvider = FutureProvider<List<CalendarResponse>>((ref) async {
return ref.read(getCalendarApiProvider).getCalendar(ref.read(startDateProvider), ref.read(endDateProvider));
});
Here is my main page:
class MainMenuScreen extends ConsumerStatefulWidget {
const MainMenuScreen({
Key? key,
}) : super(key: key);
@override
ConsumerState createState() => _MainMenuScreenState();
}
class _MainMenuScreenState extends ConsumerState<MainMenuScreen> {
String _selectedDate = '';
@override
Widget build(BuildContext context) {
print('Main Build Called');
AsyncValue<List<CalendarResponse>> calendarResponse = ref.watch(calendarResponseProvider);
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
title: const Text(
"My Sessions",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.normal),
),
actions: [
//
IconButton(
icon: Icon(
Icons.settings,
color: Colors.white,
),
onPressed: () {
ref.refresh(calendarResponseProvider);
},
)
],
centerTitle: true,
),
drawer: const DrawerWidget(),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
StartEndDateWidget(),
calendarResponse.when(data: (data) {
print('DATA length: ${data.length}');
return Expanded(
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: data.length,
itemBuilder: ((context, index) {
return Text(data[index].studentNames ?? '-');
})),
);
}, error: ((error, stackTrace) {
return Text('Error: ${error.toString()}');
}), loading: (() {
return const Center(
child: CircularProgressIndicator(),
);
}))
],
),
);
}
}
Here is the class of the api call:
class GetCalendarApi {
Future<List<CalendarResponse>> getCalendar(DateTime startTime, DateTime endTime) async {
log('startTime $startTime', name: logGetCalendarApi);
log('endTime $endTime', name: logGetCalendarApi);
String token = await SharedPrefsMethods.getStringFromPrefs(PREF_TOKEN_KEY);
String url = BASE_URL + SESSNOTE_URL + GET_CALENDAR_URL;
List<CalendarResponse> calendarResponseList;
Map<String, String> queryParameters = {
"startTime": "${formatDate(startTime)}",
"endTime": "${formatDate(endTime)}",
};
Uri uri = Uri.parse(url).replace(queryParameters: queryParameters);
log('Uri: (logGetCalendarApi) $uri', name: logGetCalendarApi);
return http.get(uri, headers: {
HttpHeaders.contentTypeHeader: "application/json",
HttpHeaders.authorizationHeader: "Bearer $token",
'accept': 'text/plain'
}).then((http.Response response) {
int statusCode = response.statusCode;
log('Status Code: $statusCode', name: logGetCalendarApi);
log('RESPONSE BODY (get students and mandates): ${response.body}', name: logGetCalendarApi);
// dynamic data = json.decode(DummyResponses.getMandatesForProvider);
dynamic data = json.decode(response.body);
if (statusCode < 200 || statusCode >= 400) {
log('Error: ${response.body}', name: logGetCalendarApi);
Fluttertoast.showToast(
msg: data['Message'] ??
'An internal error has occurred.',
toastLength: Toast.LENGTH_LONG,
timeInSecForIosWeb: 3);
throw Exception(data['Message'] ?? 'An internal error has occurred.');
}
calendarResponseList = data
.map<CalendarResponse>(
(json) => CalendarResponse.fromJson(json))
.toList();
log('Calendar result List Size: ${calendarResponseList.length}', name: logGetCalendarApi);
return calendarResponseList;
});
}
}
I call this from another screen to trigger the http request again:
ref.refresh(calendarResponseProvider);
Thanks very much
2
Answers
You need to check one more condition for that is check for
isRefreshing
for your provider variable.same as
calendarResponse.isRefreshing
AsyncValue.when
takes askipLoadingOnRefresh
boolean which defaults totrue
, causing a refresh to go directly from the first value to the second value. If you want to get a loading there in the middle, set that tofalse
.This is a breaking change between Riverpod 1 and 2, and causes some confusion, but I’m happy the default is to skip loading on refresh now. Just override that to get the classic behavior back again. Source: https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValueX/when.html