Flutter - 对带有模型的图表使用 API

Posted

技术标签:

【中文标题】Flutter - 对带有模型的图表使用 API【英文标题】:Flutter - Using API for charts with model 【发布时间】:2021-05-18 21:21:53 【问题描述】:

我被这个问题困扰了将近 3 天。已经将其发布在另一个问题中,但是得到了减号而不是答案。 让我把问题说清楚。 我想将使用模型的 api 数据放入图表颤动中,这是我的代码 首先是我的模型这里是我的年龄模型

ages.dart

  Ages agesFromJson(String str) => Ages.fromJson(json.decode(str));

  String agesToJson(Ages data) => json.encode(data.toJson());

  class Ages 
    Ages(
      this.entity,
      this.code,
      this.year,
      this.underAge15,
      this.age1564,
      this.age65Over,
    );

    String entity;
    String code;
    int year;
    String underAge15;
    String age1564;
    String age65Over;

    factory Ages.fromJson(Map<String, dynamic> json) => Ages(
          entity: json["entity"],
          code: json["code"],
          year: json["year"],
          underAge15: json["under_age_15"],
          age1564: json["age_15_64"],
          age65Over: json["age_65_over"],
        );

    Map<String, dynamic> toJson() => 
          "entity": entity,
          "code": code,
          "year": year,
          "under_age_15": underAge15,
          "age_15_64": age1564,
          "age_65_over": age65Over,
        ;
  

在这里我制作了年龄图表,该图表为我提供了来自 api 的 url 并放入未来的构建器中

ages_chart.dart

    class AgeCharts extends StatefulWidget 
      final String url;

      const AgeCharts(this.url);

      @override
      _AgeChartsState createState() => _AgeChartsState();
    

    class _AgeChartsState extends State<AgeCharts> 
      var chart;
      @override
      void initState() 
        // TODO: implement initState
        super.initState();
      

      @override
      Widget build(BuildContext context) 
        print(widget.url);
        return SafeArea(
          child: Scaffold(
            appBar: AppBar(
              title: Text('Japanese Age Working Populations'),
            ),
            body: Container(
              height: 400,
              child: FutureBuilder<List>(
                future: getChartData(widget),
                builder: (context, dataapi) 
                  if (dataapi.hasError) print(dataapi.error);
                  print(dataapi);
                  return dataapi.hasData
                      ? ShowChart(
                          data: dataapi.data,
                        )
                      : Center(
                          child: CircularProgressIndicator(),
                        );
                ,
              ),
            ),
          ),
        );
      
    

在 dart 之后,我做了一个函数来获取一个 api。

  Future<List> getChartData(widget) async 
    final response = await http.get(widget.url);
    return json.decode(response.body)['data'];
  

这是我已经获得网址时的显示图表

  class ShowChart extends StatelessWidget 
    final List data;

    ShowChart(this.data);

    static List<charts.Series<Ages, dynamic>> _createSampleData(dataAPI) 
      return [
        new charts.Series<Ages, dynamic>(
          id: 'Desktop',
          // colorFn specifies that the line will be blue.
          colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
          // areaColorFn specifies that the area skirt will be light blue.
          areaColorFn: (_, __) =>
              charts.MaterialPalette.blue.shadeDefault.lighter,
          domainFn: (Ages ages, _) => ages.year,
          measureFn: (Ages ages, _) => int.parse(ages.underAge15),
          data: dataAPI,
        ),
        new charts.Series<Ages, dynamic>(
          id: 'Tablet',
          // colorFn specifies that the line will be red.
          colorFn: (_, __) => charts.MaterialPalette.red.shadeDefault,
          // areaColorFn specifies that the area skirt will be light red.
          areaColorFn: (_, __) => charts.MaterialPalette.red.shadeDefault.lighter,
          domainFn: (Ages ages, _) => ages.year,
          measureFn: (Ages ages, _) => int.parse(ages.age1564),
          data: dataAPI,
        ),
        new charts.Series<Ages, dynamic>(
          id: 'Mobile',
          // colorFn specifies that the line will be green.
          colorFn: (_, __) => charts.MaterialPalette.green.shadeDefault,
          // areaColorFn specifies that the area skirt will be light green.
          areaColorFn: (_, __) =>
              charts.MaterialPalette.green.shadeDefault.lighter,
          domainFn: (Ages ages, _) => ages.year,
          measureFn: (Ages ages, _) => int.parse(ages.age65Over),
          data: dataAPI,
        ),
      ];
    

    @override
    Widget build(BuildContext context) 
      print('showchart');
      print(data);
      return Container(
        child: charts.LineChart(
          _createSampleData(data),
          defaultRenderer:
              new charts.LineRendererConfig(includeArea: true, stacked: true),
          animate: true,
        ),
      );
    
  

这是我的 JSON 结构。但是我在使用 json.decode(response.body) 时得到了数据


"status": true,
"message": "Chart found",
"data": [
    
        "entity": "Japan",
        "code": "JPN",
        "year": 1950,
        "under_age_15": "29288.106",
        "age_15_64": "49447.555",
        "age_65_over": "4066.423"
    ,
    
        "entity": "Japan",
        "code": "JPN",
        "year": 1951,
        "under_age_15": "29652.515",
        "age_15_64": "50461.828",
        "age_65_over": "4201.922"
    ,
   ]
 

我遇到了这样的错误

 type 'List<dynamic>' is not a subtype of type 'List<Ages>'

请帮我解决问题

【问题讨论】:

【参考方案1】:

代码中的问题:

    您将系列数据显式转换为charts.Series&lt;Ages, dynamic&gt;,而它应该是charts.Series&lt;Ages, num&gt; 您正试图将 double 解析为 int。如果你想要双打,使用 double.parse(x) 否则,double.parse(x).round() 会给你一个 int。 (或.floor().ceil()) 您可能还应该将域轴指定为非 zeroBound:
charts.LineChart(
  _createSampleData(data),
  defaultRenderer: charts.LineRendererConfig(includeArea: true, stacked: true),
  animate: true,
  domainAxis: charts.NumericAxisSpec(
    tickProviderSpec: charts.BasicNumericTickProviderSpec(zeroBound: false),
  ),
),

工作解决方案

完整的源代码,方便复制粘贴

import 'dart:math' as math;
import 'dart:convert';
import 'package:http/http.dart' as http;

import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;

void main() 
  runApp(
    MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Charts Demo',
      home:
          AgeCharts(url: 'http://udacoding-task-api.herokuapp.com/api/charts'),
    ),
  );


// MAIN WIDGET

class AgeCharts extends StatefulWidget 
  final String url;

  const AgeCharts(this.url);

  @override
  _AgeChartsState createState() => _AgeChartsState();


class _AgeChartsState extends State<AgeCharts> 
  var chart;

  Future<List<Ages>> getChartData(widget) async 
    final response = await http.get(widget.url);
    final List<dynamic> jsonData = jsonDecode(response.body)['data'];
    return jsonData.map((data) => Ages.fromJson(data)).toList();
  

  @override
  Widget build(BuildContext context) 
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: Text('Japanese Age Working Populations'),
        ),
        body: Padding(
          padding: EdgeInsets.all(8.0),
          child: FutureBuilder<List>(
            future: getChartData(widget),
            builder: (context, dataapi) 
              if (dataapi.hasError) print(dataapi.error);
              return dataapi.hasData
                  ? ShowChart(data: dataapi.data)
                  : Center(child: CircularProgressIndicator());
            ,
          ),
        ),
      ),
    );
  


// CHART

class ShowChart extends StatelessWidget 
  final List<Ages> data;

  ShowChart(this.data);

  static List<charts.Series<Ages, num>> _createSampleData(dataAPI) 
    return [
      new charts.Series<Ages, num>(
        id: 'underAge15',
        colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
        areaColorFn: (_, __) =>
            charts.MaterialPalette.blue.shadeDefault.lighter,
        domainFn: (Ages ages, _) => ages.year,
        measureFn: (Ages ages, _) => double.parse(ages.underAge15).round(),
        data: dataAPI,
      ),
      new charts.Series<Ages, num>(
        id: 'age1564',
        colorFn: (_, __) => charts.MaterialPalette.red.shadeDefault,
        areaColorFn: (_, __) => charts.MaterialPalette.red.shadeDefault.lighter,
        domainFn: (Ages ages, _) => ages.year,
        measureFn: (Ages ages, _) => double.parse(ages.age1564).round(),
        data: dataAPI,
      ),
      new charts.Series<Ages, num>(
        id: 'age65Over',
        colorFn: (_, __) => charts.MaterialPalette.green.shadeDefault,
        areaColorFn: (_, __) =>
            charts.MaterialPalette.green.shadeDefault.lighter,
        domainFn: (Ages ages, _) => ages.year,
        measureFn: (Ages ages, _) => double.parse(ages.age65Over).round(),
        data: dataAPI,
      ),
    ];
  

  @override
  Widget build(BuildContext context) 
    return Container(
      child: charts.LineChart(
        _createSampleData(data),
        defaultRenderer:
            new charts.LineRendererConfig(includeArea: true, stacked: true),
        animate: true,
        domainAxis: charts.NumericAxisSpec(
          tickProviderSpec:
              charts.BasicNumericTickProviderSpec(zeroBound: false),
        ),
      ),
    );
  


// DOMAIN

class Ages 
  Ages(
    this.entity,
    this.code,
    this.year,
    this.underAge15,
    this.age1564,
    this.age65Over,
  );

  String entity;
  String code;
  int year;
  String underAge15;
  String age1564;
  String age65Over;

  factory Ages.fromJson(Map<String, dynamic> json) => Ages(
        entity: json["entity"],
        code: json["code"],
        year: json["year"],
        underAge15: json["under_age_15"],
        age1564: json["age_15_64"],
        age65Over: json["age_65_over"],
      );

  Map<String, dynamic> toJson() => 
        "entity": entity,
        "code": code,
        "year": year,
        "under_age_15": underAge15,
        "age_15_64": age1564,
        "age_65_over": age65Over,
      ;


// DATA

math.Random random = math.Random();

final Map<String, dynamic> data = 
  "status": true,
  "message": "Chart found",
  "data": List.generate(
      20,
      (index) => 
            "entity": "Japan",
            "code": "JPN",
            "year": 1950 + index,
            "under_age_15": (25000 + random.nextInt(5000)).toString(),
            "age_15_64": (40000 + random.nextInt(5000)).toString(),
            "age_65_over": (3000 + random.nextInt(2000)).toString(),
          ).toList(),
;

答案更新前:

试试这个:

Future<List<Ages>> getChartData(widget) async 
  final response = await http.get(widget.url);
  return json.decode(response.body)['data'].map((jsonData) => Ages.fromJson(jsonData)).toList();

我不能 100% 确定,因为我不知道您的 JSON 数据的结构。

【讨论】:

感谢您的回答,我已经编辑了我的问题:D 在我将 api 更改为您的答案后,我又遇到了另一个错误,类型 'List' 不是类型 'FutureOr>' 的子类型 我在您的代码中发现了 3 个主要问题。答案已更新。 我在上面尝试了你的代码,使用数学随机,它可以生成。但是当我将它更改为我的 json 时,它变成了一个问题,发生了异常。 _CastError(类型'List'不是类型转换中'List>'类型的子类型)..顺便说一句,这是我得到我的apiudacoding-task-api.herokuapp.com/api/charts.. 我更新了解决方案以使用您的真实数据集。

以上是关于Flutter - 对带有模型的图表使用 API的主要内容,如果未能解决你的问题,请参考以下文章

Flutter tflite图像分类如何不显示错误结果

我可以在 Flutter 中使用类似 MPAndroidChart 的图表吗? [关闭]

Flutter:带有初始网络请求的 ChangeNotifierProvider

在一个视图表中对多个模型进行排序

Flutter 使用具有作用域模型的多个模型

Flutter 图表组件 charts_flutter_new