I'm facing an issue trying to use FutureBuilder with Provider and cache. The computation is done one time and then its cached. The code looks like this:
FutureBuilder(
future: model.calculateStats(chartType: getChartType()),
builder: (context, snapshot) {
if(snapshot.connectionState != ConnectionState.done) {
return Center(
child: CircularProgressIndicator()
);
}
return buildChart(model);
},
)
That piece of code is inside a Consumer with a ViewModel, which has the method calculateStats
, which is the following:
Future calculateStats({ChartType chartType = ChartType.Monthly}) async {
return await MemoryCache.instance.getOrCreateAsync("stats:${chartType.index}", () async {
var statsMaker = StatsMaker(chartType: chartType);
this._currentStats = await statsMaker.generate(
startDate: _getStartDateForChartType(chartType),
endDate: DateTime.now()
);
}, allowNull: true);
}
Here you can see a video of what is happening: https://i.imgur.com/SWQ7N7P.mp4
The helper class MemoryCache checks if the provided key is in a map, and if that is true, it returns the value without doing any computation, which should return immediately, if it not found, the future is awaited and the result stored. But here, although the result is cached, the FutureBuilder shows the loading indicator (which is orange in the video). What am I doing wrong?
Their might be 2 reasons FutureBuilder
keep loading. One is because the calculateStats
keep firing, that cause the snapshot
's status to be refreshed repeatedly. Second is your getOrCreateAsync
might return an already completed Future
, that the FutureBuilder
has no way to synchronously determine that a Future has already completed.
There are a more convenient way to cached and load the UI one-time, which is using StreamBuilder
since you only need to call the async
method once in the initState
or didChangeDependencies
, hence the UI don't need to be reload all the time.
You should use the snapshot.hasData
as well to check if the Future value is completed and not-null.
In the UI:
@override
initState() {
super.initState();
model.calculateStats(chartType: getChartType());
}
// ... other lines
return StreamBuilder(
stream: model.statsStream,
builder: (context, snapshot) {
if(!snapshot.hasData) {
return Center(
child: CircularProgressIndicator()
);
}
return buildChart(snapshot.data);
},
)
In your async
function:
StreamController _controller = StreamController();
Stream get statStream => _controller.stream;
Future calculateStats({ChartType chartType = ChartType.Monthly}) async {
final stats = await MemoryCache.instance.getOrCreateAsync( ... );
// add your stats to Stream here (stats is the value you want to send to UI)
_controller.add(stats);
Make sure that your getOrCreateAsync
return a non-null value so that the Stream is updated correctly.