Search code examples
flutterdarttypeerrorsyncfusionsyncfusion-chart

Flutter + syncfusion charts: Is it possible to have a dataSource that is not a List? (type 'BanditData' is not a subtype of type 'List<BanditData>')


I am trying to display a bar chart in my app with the syncfusion library. It contains 6 bars where the height is defined by a score and the name is a player name. I have the following methods: getBanditBarData() which gets the data from a database in creates a list of BanditData-objects (BanditData class shown below), and barChart() which creates a List of ChartSeries that I can return in the series parameter of my SfCartesianChart.

My problem is that the item of the dataSource: item-line in my barChart()-method gives the following exception:

_TypeError (type 'BanditData' is not a subtype of type 'List<BanditData>')

I've tried nesting an additional List around each BanditData object in the list, and even removing the for-loop of the method. Both changes result in similar errors somewhere in the same method.

  Future<List<BanditData>> getBanditBarData() async {
    var scores = await database.totalScore();

    List<BanditData> banditData = [];

    for (var score in scores) {
      BanditData bandit = BanditData(score['name'], "", score['score']);
      banditData.add(bandit);
    }

    return banditData;
  }

  List<ChartSeries> barChart(data) {
    var barList = <ChartSeries>[];

    for (var item in data) {
      barList.add(BarSeries<BanditData, String>(
          dataSource: item,
          xValueMapper: (BanditData b, _) => removeBanditSuffix(b.name),
          yValueMapper: (BanditData b, _) => b.score,
          animationDuration: 2000));
    }

    return barList;
  }

The BanditData-class is very simple and looks like this:

class BanditData {
  BanditData(this.name, this.date, this.score);

  final String name;
  final String date;
  final int score;
}

The setup shown above works when I render my line chart. The methods are very similar:

  Future<List<List<BanditData>>> getBanditLineData() async {
    var dates = await database.getDistinctDatesList();
    var scores = await database.createScoreDataStruct();

    List<List<BanditData>> banditData = [];

    for (var i = 0; i < scores.length; i++) {
      List<BanditData> temp = [];
      var intList = scores[i]['scores'];
      for (var j = 0; j < scores[i]['scores'].length; j++) {
        BanditData bandit = BanditData(scores[i]['name'], dates[j], intList[j]);
        temp.add(bandit);
      }
      banditData.add(temp);
    }

    return banditData;
  }

  List<ChartSeries> lineChart(data) {
    var lineList = <ChartSeries>[];

    for (var item in data) {
      lineList.add(LineSeries<BanditData, String>(
        dataSource: item,
        xValueMapper: (BanditData b, _) => b.date,
        yValueMapper: (BanditData b, _) => b.score,
        enableTooltip: true,
        name: removeBanditSuffix(item[1].name),
        width: 3.0,
        animationDuration: 2000,
      ));
    }

    return lineList;
  }

If necessary, here is some more code showing how I build the chart. The above methods is placed inside MyStatsPageState, but figured it would be better to split it up for readability.

Ideally, I should be able to replace series: lineChart(lineData) with series: barChart(barData):

import 'database.dart';

import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_charts/charts.dart';

class MyStatsPage extends StatefulWidget {
  const MyStatsPage({Key? key}) : super(key: key);

  @override
  MyStatsPageState createState() {
    return MyStatsPageState();
  }
}


class MyStatsPageState extends State<MyStatsPage> {
  late Future<List<List<BanditData>>> _banditLineData;
  late Future<List<BanditData>> _banditBarData;
  final database = Database();
  bool displayLineChart = true;

  @override
  void initState() {
    _banditLineData = getBanditLineData();
    _banditBarData = getBanditBarData();
    super.initState();

    getBanditBarData();
  }

  @override
  Widget build(BuildContext context) {
    const appTitle = "Stats";

    return Scaffold(
        appBar: AppBar(
            title: const Text(
          appTitle,
          style: TextStyle(fontSize: 25, fontWeight: FontWeight.w700),
        )),
        body: FutureBuilder(
            future: Future.wait([_banditLineData, _banditBarData]),
            builder: (context, AsyncSnapshot snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return const Center(
                  child: CircularProgressIndicator(),
                );
              } else {
                if (snapshot.hasError) {
                  return ErrorWidget(Exception(
                      'Error occured when fetching data from database'));
                } else if (!snapshot.hasData) {
                  return const Center(child: Text('No data found.'));
                } else {
                  final lineData = snapshot.data![0];
                  final barData = snapshot.data![1];

                  return Padding(
                      padding: const EdgeInsets.all(5.0),
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                        children: [
                          Expanded(
                              child: SfCartesianChart(
                            primaryXAxis: CategoryAxis(),
                            enableAxisAnimation: true,
                            series: lineChart(lineData),
                          )),
                        ],
                      ));
                }
              }
            }));
}

Solution

  • We have checked the code snippet attached in the query and found that it is a sample-level issue, it occurs due to you have passed the BanditData instead of List. Because dataSource property always supports the list value only. To resolve this, convert the barData to nested lists, or assign a value to the BarSeries dataSource like below.

    Code snippet:

    List<ChartSeries> barChart(data) {
      var barList = <ChartSeries>[];
      for (var item in data) {
        barList.add(BarSeries<BanditData, String>(
            dataSource: [item],
            // Other required properties
        ));
      }
      return barList;
    }