Memory leaks after adding `touchCallback` and `checkToShowDot`
- Dominant language
- Dart
- Stars
- 7.6k
- Forks
- 2k
- Avg merge
- 9d 1h
- Merged PRs (30d)
- 2
Description
# Don't make a duplicate issue.
I'm not sure if its the same as this issue as it only happened after I used the callbacks in the title and the example in that issue doesn't use them: https://github.com/imaNNeo/fl_chart/issues/1106
If it is the same I can close this and record it over there.
# **Describe the bug**
I have multiple charts that all show relating data. The data in this example has around 1500 points per line and I have tested up to 5 lines, so is fairly large. When I hover over one I wanted to show "spots" on the others.
To do this I added a `touchCallback` that saves the hovered spot locations in a riverpod `NotifierProvider`. All charts watch this and then in their `checkToShowDot` it checks for the spots that relate to the hover data in the `NotifierProvider` and then draws them.
I have noticed that the flutter app started using lots of RAM after I added this hover functionality. I have seen over 2Gb and climbing in apples activity monitor. The app starts between 200-300mb. I opened the memory page of devtools and it shows that there where millions of FlSpot objects and they climb in numbers fast when hovering on a chart. It suffers from frame jank and gets slower and slower the more you hover until it becomes unusable.
It is possible I'm misusing the API as I'm new to Dart/Flutter.
# **To Reproduce**
This is one of the 4 charts in the video (the middle one of the 3 on the right side). All 4 charts are almost identical to this.
``` dart
class SpeedChart extends Chart {
const SpeedChart({super.key, super.showXAxis});
@override
ChartType get chartType => ChartType.speed;
@override
Widget build(BuildContext context, WidgetRef ref) {
final HoverLocations hoverData = ref.watch(hoverProvider);
final RunData runData = ref.watch(runDataProvider);
if (runData.isEmpty)
{
return const SizedBox.shrink();
}
return LineChart(
LineChartData(
lineTouchData: LineTouchData(
handleBuiltInTouches: true,
touchSpotThreshold: 20,
touchCallback: (final FlTouchEvent ev, final LineTouchResponse? res) =>
notifyHoverLocations(ref, ev, res),
),
gridData: const FlGridData(show: false),
titlesData: getTitles(false, "Speed (MPH)"),
borderData: FlBorderData(show: false),
lineBarsData: _buildLineData(hoverData, runData),
),
);
}
List _buildLineData(
final HoverLocations hoverData,
final RunData runData) {
final List lineData = [];
for (final (index, run) in runData.runNames.indexed) {
if (runData.getIsActive(run)) {
lineData.add(LineChartBarData(
isCurved: false,
barWidth: 1.5,
color: (index < chartLineColours.length) ? chartLineColours[index] : null,
dotData: FlDotData(
show: true,
checkToShowDot: (final FlSpot spot, final LineChartBarData barData) =>
showDotsForHover(hoverData, spot, barData),
),
spots: () {
final List distances = runData.getDistances(run);
final List speeds = runData.getSpeeds(run);
return List.generate(
distances.length,
(index) {
return FlSpot(distances[index], speeds[index]);
}
);
}()
));
}
}
return lineData;
}
}
```
These two functions are in a parent class:
``` dart
void notifyHoverLocations(
final WidgetRef ref,
final FlTouchEvent ev,
final LineTouchResponse? res) {
if (!ev.isInterestedForInteractions)
{
ref.read(hoverProvider.notifier).setHoveredChart(null);
}
else
{
for (final TouchLineBarSpot hoverSpot in res?.lineBarSpots ?? []) {
ref.read(hoverProvider.notifier).addOrUpdateLineHover(
hoverSpot.bar.color?.value ?? 0, hoverSpot.x, hoverSpot.spotIndex);
}
ref.read(hoverProvider.notifier).setHoveredChart(chartType);
}
}
bool showDotsForHover(
final HoverLocations hoverData,
final FlSpot spot,
final LineChartBarData barData) {
if ((hoverData.currentHoveredChart ?? chartType) != chartType &&
hoverData.colourHasHover(barData.color?.value ?? 0) &&
spot.x == hoverData.getHoverLocation(barData.color?.value ?? 0).$1) {
return true;
}
return false;
}
```
Before adding the two callbacks I mention above I just had this and the FlSpots never change from `27093` for 5 lines:
``` dart
lineTouchData: const LineTouchData(
handleBuiltInTouches: true,
touchSpotThreshold: 50,
)
```
# **Screenshots**
Video of the charts and how they work (you can see there is major frame jank at times):
https://github.com/imaNNeo/fl_chart/assets/19472253/e42fbd6f-311e-435f-9c4f-a7cd1b5db3b0
A snap of the number of FlSpot objects:
Hovered for about 10 seconds and took a snap of the number of FlSpot objects again:
# **Versions**
```
Flutter 3.19.5 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 300451adae (5 weeks ago) • 2024-03-27 21:54:07 -0500
Engine • revision e76c956498
Tools • Dart 3.3.3 • DevTools 2.31.1
```
```
fl_chart: ^0.67.0
```
Contributor guide
Research direction
Start with the supplied SpeedChart reproduction and compare it with issue 1106, focusing on the touchCallback and checkToShowDot paths. Profile FlSpot counts and frame jank while hovering, then determine whether the reported memory growth is caused by these callbacks; done means the cause is confirmed and the leak or regression is resolved or documented.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- dart, flutter
- Domain
- data-visualization, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100