Description
Bug description
Dear Syncfusion Team,
We sincerely appreciate the robust functionality provided by your libraries. Our development team is extensively utilizing the syncfusion_flutter_charts package in our application.
We have encountered a rendering issue specifically on the Android platform, while the same implementation functions seamlessly on iOS. For your reference, we have attached screenshots illustrating the issue. Notably, there are no error messages or logs to analyze, as the charts simply fail to render on Android.
We kindly request your assistance in diagnosing and resolving this issue. Please let us know if additional details or specific configurations are required to facilitate your investigation.
Steps to reproduce
App: https://play.google.com/store/apps/details?id=com.ppl.zeroxppl&hl=en_IN
Code sample
Code sample
class LineCandleChart extends StatefulWidget {
final List<List<num>> chartData;
final List<LineChartPointData> buyPointsToPlot;
final List<LineChartPointData> sellPointsToPlot;
final Color? graphColor;
final bool isYAxisVisible;
final bool isXAxisVisible;
final bool isDateTimeXAxis;
final void Function(double?)? onTrackballChange;
final TrendingChartFilterType trendingChartFilterType;
final bool isCandleStick;
final bool isShowingValueInTooltip;
final void Function(ChartTouchInteractionArgs)? onChartTouchInteractionDown;
final void Function(ChartTouchInteractionArgs)? onChartTouchInteractionUp;
final bool isTimeInSeconds;
final bool showLiveBubble;
final TokenPriceChartUtils? tokenPriceChartUtils;
final bool showMajorGridLinesXAxis;
final bool showMajorGridLinesYAxis;
final bool showMajorTickLines;
final bool showAxisLine;
final bool showLabel;
final bool showDateTimeXAxisInTime;
final bool isFromFeed;
final TrendingBuySellPoints? trendingBuySellPoints;
final Map<String, PeopleModel>? identities;
final Function(PeopleModel)? onPointTap;
const LineCandleChart({
super.key,
required this.chartData,
this.graphColor,
this.buyPointsToPlot = const [],
this.sellPointsToPlot = const [],
this.isYAxisVisible = false,
this.isXAxisVisible = false,
this.onTrackballChange,
required this.isCandleStick,
required this.trendingChartFilterType,
required this.isShowingValueInTooltip,
this.onChartTouchInteractionDown,
this.onChartTouchInteractionUp,
this.isDateTimeXAxis = false,
this.showLiveBubble = false,
this.isTimeInSeconds = false,
this.tokenPriceChartUtils,
this.showMajorGridLinesXAxis = true,
this.showMajorGridLinesYAxis = false,
this.showAxisLine = true,
this.showMajorTickLines = true,
this.showLabel = false,
this.showDateTimeXAxisInTime = false,
this.isFromFeed = false,
this.trendingBuySellPoints,
this.identities,
this.onPointTap,
});
@override
State<LineCandleChart> createState() => _LineCandleChartState();
}
class _LineCandleChartState extends State<LineCandleChart> {
bool isTrackballChanging = false;
@override
Widget build(BuildContext context) {
LineChartUtils lineChartUtils = LineChartUtils();
LineChartDataModel? lineChartDataModel =
lineChartUtils.getGradientSeriesData(
chartData: widget.chartData,
context: context,
chartColor: getColor(context),
isTimeInSeconds: widget.isTimeInSeconds,
buyPointsToPlot: widget.buyPointsToPlot,
sellPointsToPlot: widget.sellPointsToPlot,
isCandleStick: widget.isCandleStick,
isDateTimeXAxis: widget.isDateTimeXAxis,
showLiveBubble: widget.showLiveBubble,
tokenPriceChartUtils: widget.tokenPriceChartUtils,
isTrackballChanging: isTrackballChanging,
trendingBuySellPoints: widget.trendingBuySellPoints,
identities: widget.identities,
onPointTap: widget.onPointTap,
);
if (widget.chartData.length < 2 || lineChartDataModel == null) {
return const SizedBox.shrink();
}
int? lastHapticPointIndex = 0;
return RepaintBoundary(
child: SfCartesianChart(
key: widget.key,
title: const ChartTitle(text: ''),
plotAreaBorderWidth: 0,
primaryXAxis: widget.isDateTimeXAxis
? DateTimeAxis(
isVisible: widget.isXAxisVisible,
majorGridLines: const MajorGridLines(
width: 0,
),
title: const AxisTitle(),
labelFormat: '{value}',
dateFormat: DateTimeUtils.formatGraphTimeDifference(
widget.chartData,
),
minimum: DateTime.fromMillisecondsSinceEpoch(
lineChartDataModel.minX,
),
maximum: DateTime.fromMillisecondsSinceEpoch(
lineChartDataModel.maxX,
),
labelStyle: Theme.of(context).textTheme.displaySmall?.copyWith(
color: Theme.of(context).colorScheme.text3,
fontSize: 11,
),
axisLine: widget.showAxisLine
? AxisLine(
width: 0.1,
color: Theme.of(context).colorScheme.text2,
)
: const AxisLine(
width: 0,
),
majorTickLines: MajorTickLines(
size: 0,
color: Theme.of(context).colorScheme.text2,
),
desiredIntervals: 4,
tickPosition: TickPosition.outside,
axisBorderType: AxisBorderType.withoutTopAndBottom,
rangePadding: ChartRangePadding.auto,
labelsExtent: 70,
labelPosition: ChartDataLabelPosition.outside,
placeLabelsNearAxisLine: true,
edgeLabelPlacement: EdgeLabelPlacement.none,
intervalType: DateTimeIntervalType.auto,
)
: NumericAxis(
isVisible: widget.isXAxisVisible,
labelFormat: '{value}',
desiredIntervals: DateTimeUtils.getLastTimeDuration(
widget.chartData,
).inHours >
48 &&
DateTimeUtils.getLastTimeDuration(
widget.chartData,
).inDays <
4
? 3
: 4,
axisLabelFormatter: (value) {
if (widget.isFromFeed) {
return ChartAxisLabel(
DateTimeUtils.formatGraphTimeDifference(
widget.chartData,
).format(
DateTime.fromMillisecondsSinceEpoch(
value.value.toInt(),
),
),
value.textStyle,
);
}
return ChartAxisLabel(
NumberUtils.abbreviateNumberForGraph(value.value),
value.textStyle,
);
},
majorGridLines: widget.showMajorGridLinesXAxis
? const MajorGridLines()
: const MajorGridLines(
width: 0,
),
axisLine: widget.showAxisLine
? const AxisLine()
: const AxisLine(
width: 0,
),
majorTickLines: widget.showMajorTickLines
? const MajorTickLines()
: const MajorTickLines(
size: 0,
),
title: const AxisTitle(),
minimum: lineChartDataModel.minX.toDouble(),
maximum: lineChartDataModel.maxX.toDouble(),
tickPosition: TickPosition.inside,
labelStyle: Theme.of(context).textTheme.displaySmall?.copyWith(
color: Theme.of(context).colorScheme.text3,
fontSize: 10,
),
plotOffsetEnd: 24,
rangePadding: ChartRangePadding.auto,
edgeLabelPlacement: EdgeLabelPlacement.shift,
),
primaryYAxis: NumericAxis(
isVisible: widget.isYAxisVisible,
opposedPosition: true,
maximumLabels: 2,
majorGridLines: MajorGridLines(
width: widget.showMajorGridLinesYAxis ? 2 : 0.7,
color: Theme.of(context).colorScheme.borderLight,
),
numberFormat: NumberFormat.compact(),
labelStyle: Theme.of(context).textTheme.displaySmall!.copyWith(
color: Theme.of(context).colorScheme.text3,
fontSize: 11,
),
axisLine: const AxisLine(width: 0),
majorTickLines: const MajorTickLines(size: 0),
axisLabelFormatter: (value) {
return ChartAxisLabel(
value.value == 0 ? "" : value.text,
value.textStyle,
);
},
title: const AxisTitle(),
tickPosition: TickPosition.outside,
rangePadding: ChartRangePadding.round,
edgeLabelPlacement: EdgeLabelPlacement.shift,
),
onTrackballPositionChanging: (TrackballArgs args) {
updateTrackballState(true);
if (lastHapticPointIndex != args.chartPointInfo.dataPointIndex) {
HapticFeedback.selectionClick();
lastHapticPointIndex = args.chartPointInfo.dataPointIndex;
}
if (widget.onTrackballChange != null) {
if (widget.isCandleStick) {
widget.onTrackballChange!(
args.chartPointInfo.chartPoint?.close?.toDouble(),
);
} else {
widget.onTrackballChange!(
args.chartPointInfo.chartPoint?.y?.toDouble(),
);
}
}
},
onChartTouchInteractionDown: (ChartTouchInteractionArgs args) {
HapticFeedback.mediumImpact();
if (widget.onChartTouchInteractionDown != null) {
updateTrackballState(true);
widget.onChartTouchInteractionDown!(args);
}
},
onChartTouchInteractionUp: (ChartTouchInteractionArgs args) {
updateTrackballState(false);
if (widget.onTrackballChange != null) {
widget.onTrackballChange!(null);
}
if (widget.onChartTouchInteractionUp != null) {
widget.onChartTouchInteractionUp!(args);
}
},
series: lineChartDataModel.data,
trackballBehavior: TrackballBehavior(
enable: true,
lineWidth: 0.5,
activationMode: ActivationMode.singleTap,
tooltipAlignment: ChartAlignment.near,
tooltipDisplayMode: TrackballDisplayMode.groupAllPoints,
lineDashArray: const <double>[5, 5],
tooltipSettings: InteractiveTooltip(
format: 'point.x',
color: Theme.of(context).colorScheme.baseColor,
connectorLineColor: Theme.of(context).colorScheme.baseColor,
textStyle: Theme.of(context).textTheme.displaySmall!.copyWith(
color: Theme.of(context).colorScheme.text1,
),
),
builder: (BuildContext context, TrackballDetails details) {
if (widget.isCandleStick) {
return tooltipWidgetForCandle(context, details);
}
return tooltipWidgetForLineChart(context, details);
},
markerSettings: TrackballMarkerSettings(
markerVisibility: TrackballVisibilityMode.visible,
height: 12,
width: 12,
borderWidth: 12,
borderColor: getColor(context).withOpacityValue(0.2),
color: getColor(context),
shape: DataMarkerType.circle,
),
),
zoomPanBehavior: ZoomPanBehavior(
enablePinching: true,
zoomMode: ZoomMode.x,
enableMouseWheelZooming: true,
enableSelectionZooming: false,
enableDoubleTapZooming: true,
),
),
);
}
void updateTrackballState(bool isTrackballChange) {
if (isTrackballChanging != isTrackballChange) {
isTrackballChanging = isTrackballChange;
}
}
bool get isPositive {
final firstValue = widget.chartData.first;
final index = firstValue.length > 4 ? 4 : 1;
return widget.chartData.first[index] < widget.chartData.last[index];
}
Color getColor(BuildContext context) {
if (widget.showLiveBubble && !widget.isFromFeed) {
return Theme.of(context).colorScheme.brightness == Brightness.light
? Theme.of(context).colorScheme.primary
: Colors.white;
}
if (widget.graphColor != null) {
return widget.graphColor!;
}
return isPositive
? Theme.of(context).colorScheme.success
: Theme.of(context).colorScheme.error;
}
Widget tooltipWidgetForLineChart(
BuildContext context,
TrackballDetails trackballDetails,
) {
if (trackballDetails.groupingModeInfo == null ||
(trackballDetails.groupingModeInfo?.visibleSeriesIndices.isEmpty ??
true) ||
(trackballDetails.groupingModeInfo?.visibleSeriesList.isEmpty ??
true)) {
return const SizedBox.shrink();
}
final index = trackballDetails.groupingModeInfo!.currentPointIndices.first;
if (index < 0 ||
index >=
trackballDetails
.groupingModeInfo!.visibleSeriesList.first.dataSource.length) {
return const SizedBox.shrink();
}
LineChartPointData? point = trackballDetails
.groupingModeInfo!.visibleSeriesList.first.dataSource[index];
if (point == null) {
return const SizedBox.shrink();
}
final formattedTime = DateTimeUtils.formatCustomDateForGraph(
point.valueX.toInt(),
widget.trendingChartFilterType,
);
final formattedValue = NumberUtils.abbreviateNumberForGraph(
point.valueY ?? 0,
);
return Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.baseColor,
borderRadius: BorderRadius.circular(4),
),
child: Text(
widget.isShowingValueInTooltip
? "\$$formattedValue • $formattedTime"
: formattedTime,
style: Theme.of(context).textTheme.displaySmall,
),
);
}
Widget tooltipWidgetForCandle(
BuildContext context,
TrackballDetails trackballDetails,
) {
if (trackballDetails.seriesIndex != 0) {
return const SizedBox.shrink();
}
final high = trackballDetails.point?.high == null
? null
: NumberUtils.abbreviateNumberForGraph(
trackballDetails.point!.high!,
);
final low = trackballDetails.point?.low == null
? null
: NumberUtils.abbreviateNumberForGraph(
trackballDetails.point!.low!,
);
final open = trackballDetails.point?.open == null
? null
: NumberUtils.abbreviateNumberForGraph(
trackballDetails.point!.open!,
);
final close = trackballDetails.point?.close == null
? null
: NumberUtils.abbreviateNumber(trackballDetails.point!.close!);
final formattedTime = DateTimeUtils.formatCustomDateForGraph(
trackballDetails.point?.x?.toInt(),
widget.trendingChartFilterType,
);
getTextSpan(title, subtitle) {
return TextSpan(
children: [
TextSpan(
text: title,
style: Theme.of(context)
.textTheme
.displaySmall!
.copyWith(fontWeight: FontWeight.w600),
),
TextSpan(
text: subtitle,
style: Theme.of(context).textTheme.displaySmall,
),
],
);
}
return Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.baseColor,
borderRadius: BorderRadius.circular(4),
),
child: RichText(
text: TextSpan(
style: Theme.of(context).textTheme.displaySmall,
children: [
getTextSpan('Date: ', "$formattedTime\n"),
getTextSpan('High: ', "$high\n"),
getTextSpan('Low: ', "$low\n"),
getTextSpan('Open: ', "$open\n"),
getTextSpan('Close: ', "$close"),
],
),
),
);
}
}
Screenshots or Video
Stack Traces
Stack Traces
NA
On which target platforms have you observed this bug?
Android
Flutter Doctor output
Doctor output
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.29.3, on macOS 15.3.2 24D81 darwin-arm64, locale en-IN)
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 16.3)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2024.3)
[✓] IntelliJ IDEA Ultimate Edition (version 2025.1)
[✓] VS Code (version 1.99.3)
[✓] Connected device (4 available)
! Error: Browsing on the local area network for Praveen Agrawal’s iPhone. Ensure the device is
unlocked and attached with a cable or associated with the same local area network as this
Mac.
The device must be opted into Developer Mode to connect wirelessly. (code -27)
[✓] Network resources
• No issues found!