Flutter - 为 API 实现 JSON getter

Posted

技术标签:

【中文标题】Flutter - 为 API 实现 JSON getter【英文标题】:Flutter - Implementing JSON getter for API 【发布时间】:2019-02-07 17:14:51 【问题描述】:

我正在尝试将 API found here 实现到我的 home_page.dart 中,使用 this flutter learners project 进行 JSON 解析。你可以在this GitHub Repo找到完整的代码

查看下面的当前图片,我希望最终项目“Trending News”部分从 API 返回文章图片,以及标题而不是“Item 1”、“Item 2”等...

我在使用这个版本时遇到了问题,因为当我尝试运行调试时,我遇到了问题; The getter length was called on null

home_page.dart;

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:one/cryptoData/crypto_data.dart';
import 'package:one/modules/crypto_presenter.dart';
import 'main.dart';
import 'dart:convert';
import 'background.dart';
import 'dart:async';

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

Future<List<Photo>> fetchPhotos(http.Client client) async 
  final response =
      await client.get('https://newsapi.org/v2/top-headlines?sources=crypto-coins-news&apiKey=d40a757cfb2e4dd99fc511a0cbf59098');

  // Use the compute function to run parsePhotos in a separate isolate
  return compute(parsePhotos, response.body);


// A function that will convert a response body into a List<Photo>
List<Photo> parsePhotos(String responseBody) 
  final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();

  return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();


class Photo 
  final int albumId;
  final int id;
  final String title;
  final String url;
  final String thumbnailUrl;

  Photo(this.albumId, this.id, this.title, this.url, this.thumbnailUrl);

  factory Photo.fromJson(Map<String, dynamic> json) 
    return Photo(
      albumId: json['albumId'] as int,
      id: json['id'] as int,
      title: json['title'] as String,
      url: json['url'] as String,
      thumbnailUrl: json['thumbnailUrl'] as String,
    );
  


class HomePage extends StatefulWidget 
  @override
  _HomePageState createState() => new _HomePageState();


class _HomePageState extends State<HomePage> implements CryptoListViewContract 
  CryptoListPresenter _presenter;
  List<Crypto> _currencies;
  bool _isLoading;
  final List<MaterialColor> _colors = [Colors.blue, Colors.indigo, Colors.red];

  final List<Photo> photos;

  _HomePageState() 
    _presenter = new CryptoListPresenter(this);
  


    List<String> items = [
    "Item 1",
    "Item 2",
    "Item 3",
    "Item 4",
    "Item 5",
    "Item 6",
    "Item 7",
    "Item 8"
  ];

  @override
  void initState() 
    super.initState();
    _isLoading = true;
    _presenter.loadCurrencies();
  

  @override
  Widget build(BuildContext context) 
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Crypto App",
          style: new TextStyle(
            color: Colors.white,
            fontFamily: 'Poppins',
            fontSize: 22.5,
            ),
        ),
        backgroundColor: const Color(0xFF273A48),
        elevation: 0.0,
        centerTitle: true,
      ),
      body: _isLoading
            ? new Center(
          child: new CircularProgressIndicator(),
        )
        : _allWidget()
    );
  

  Widget _allWidget() 
  final _width = MediaQuery.of(context).size.width;
  final _height = MediaQuery.of(context).size.height;
//CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED
   final headerList = new ListView.builder(
      itemBuilder: (context, index) 
        EdgeInsets padding = index == 0?const EdgeInsets.only(
            left: 20.0, right: 10.0, top: 4.0, bottom: 30.0):const EdgeInsets.only(
            left: 10.0, right: 10.0, top: 4.0, bottom: 30.0);

        return new Padding(
          padding: padding,
          child: new InkWell(
            onTap: () 
              print('Card selected');
            ,
            child: new Container(
              decoration: new BoxDecoration(
                borderRadius: new BorderRadius.circular(10.0),
                color: Colors.lightGreen,
                boxShadow: [
                  new BoxShadow(
                      color: Colors.black.withAlpha(70),
                      offset: const Offset(3.0, 10.0),
                      blurRadius: 15.0)
                ],
                image: new DecorationImage(
                  image: new ExactAssetImage(
                      'assets/img_$index%items.length.jpg'),
                  fit: BoxFit.fitHeight,
                ),
              ),
//                                    height: 200.0,
              width: 200.0,
              child: new Stack(
                children: <Widget>[
                  new Align(
                    alignment: Alignment.bottomCenter,
                    child: new Container(
                        decoration: new BoxDecoration(
                            color: const Color(0xFF273A48),
                            borderRadius: new BorderRadius.only(
                                bottomLeft: new Radius.circular(10.0),
                                bottomRight: new Radius.circular(10.0))),
                        height: 30.0,
                        child: new Row(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: <Widget>[
                            new Text(
                              '$items[index%items.length]',
                              style: new TextStyle(color: Colors.white),
                            )
                          ],
                        )),
                  )
                ],
              ),
            ),
          ),
        );
      ,
      scrollDirection: Axis.horizontal,
      itemCount: photos.length,
    );

      final body = new Scaffold(
      backgroundColor: Colors.transparent,
      body: new Container(
        child: new Stack(
          children: <Widget>[
            new Padding(
              padding: new EdgeInsets.only(top: 10.0),
              child: new Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisSize: MainAxisSize.max,
                mainAxisAlignment: MainAxisAlignment.start,
                children: <Widget>[
                  new Align(
                    alignment: Alignment.centerLeft,
                    child: new Padding(
                        padding: new EdgeInsets.only(left: 10.0,),
                        child: new Text(
                          "Trending News",
                            style: new TextStyle(
                            letterSpacing: 0.8,
                            fontFamily: 'Kanit',
                            fontSize: 17.5,
                            color: Colors.white,
                          ),
                        )
                      ),
                  ),
                  new Container(
                      height: 300.0, width: _width, child: headerList),
                  new Expanded(child:
                  ListView.builder(
                    itemBuilder: (BuildContext context, int index) 
                  final int i = index;
                  final Crypto currency = _currencies[i];
                  final MaterialColor color = _colors[i % _colors.length];
                    return new ListTile(
                      title: new Column(
                        children: <Widget>[
                          new Row(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: <Widget>[
                              new Container(
                                height: 72.0,
                                width: 72.0,
                                decoration: new BoxDecoration(
                                    color: Colors.white,
                                    boxShadow: [
                                      new BoxShadow(
                                          color:
                                          Colors.black.withAlpha(80),
                                          offset: const Offset(2.0, 2.0),
                                          blurRadius: 15.0)
                                    ],
                                    borderRadius: new BorderRadius.all(
                                        new Radius.circular(35.0)),
                                    image: new DecorationImage(
                                      image: new ExactAssetImage(
                                        "cryptoiconsBlack/"+currency.symbol.toLowerCase()+"@2x.png",
                                      ),
                                      fit: BoxFit.cover,
                                    )),
                              ),
                              new SizedBox(
                                width: 8.0,
                              ),
                              new Expanded(
                                  child: new Column(
                                    mainAxisAlignment:
                                    MainAxisAlignment.start,
                                    crossAxisAlignment:
                                    CrossAxisAlignment.start,
                                    children: <Widget>[
                                      new Text(
                                        currency.name,
                                        style: new TextStyle(
                                            fontSize: 14.0,
                                            fontFamily: 'Poppins',
                                            color: Colors.black87,
                                            fontWeight: FontWeight.bold),
                                      ),
                                      _getSubtitleText(currency.price_usd, currency.percent_change_1h),
                                    ],
                                  )),

                            ],
                          ),
                          new Divider(),
                        ],
                      ),
                    );
                  ))
                ],
              ),
            ),
          ],
        ),
      ),
    );

        return new Container(
      decoration: new BoxDecoration(
        color: const Color(0xFF273A48),
      ),
      child: new Stack(
        children: <Widget>[
          new CustomPaint(
            size: new Size(_width, _height),
            painter: new Background(),
          ),
          body,
        ],
      ),
    );



  

// CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED


  Widget _getSubtitleText(String priceUSD, String percentageChange) 
    TextSpan priceTextWidget = new TextSpan(
        text: "\$$priceUSD\n", style: new TextStyle(color: Colors.black));
    String percentageChangeText = "1 hour: $percentageChange%";
    TextSpan percentageChangeTextWidget;

    if (double.parse(percentageChange) > 0) 
      percentageChangeTextWidget = new TextSpan(
          text: percentageChangeText,
          style: new TextStyle(color: Colors.green));
     else 
      percentageChangeTextWidget = new TextSpan(
          text: percentageChangeText, style: new TextStyle(color: Colors.red));
    

    return new RichText(
        text: new TextSpan(
            children: [priceTextWidget, percentageChangeTextWidget]));
  


  //DONT TOUCH, Works with cryptoListViewContract implimentation in _MyHomePageState
    @override
  void onLoadCryptoComplete(List<Crypto> items) 
    // TODO: implement onLoadCryptoComplete

    setState(() 
      _currencies = items;
      _isLoading = false;
    );
  

  @override
  void onLoadCryptoError() 
    // TODO: implement onLoadCryptoError
  


应用程序的最后工作版本;

谢谢,杰克

【问题讨论】:

你尝试过用断点调试你的代码吗? 不,我不知道它们是如何工作的,所以我从不使用它们 【参考方案1】:

列表photos 在您的情况下为空。它没有在任何地方初始化。我想你忘了调用返回List&lt;Photo&gt;fetchPhotos() 方法。所以才会出现空指针异常。

【讨论】:

嘿,Vinoth,您能否使用提供的赏金说明重新修改您的答案。谢谢 你能发布你的完整项目吗?因为我无法编译这个类,因为缺少 3 个左右的类。 好的,感谢您的关注,我将能够在 5 小时左右发布 github 存储库。抱歉让您久等了 没问题@Jake。那就等你的帖子吧。 好的,非常感谢。我现在不在,但我会尽快解决【参考方案2】:

尝试改变你拥有的任何地方

.length

(... ?.length ?? 0)

喜欢

itemCount: photos?.length ?? 0,

final MaterialColor color = _colors[i % (_colors?.length ?? 0)];

'assets/img_$index % (items?.length ?? 0).jpg'),

...

【讨论】:

无论如何你可以提供我在上面的问题中留下的代码,所有这些都改变了吗?我目前正在研究我项目的另一个方面,并希望保持我的思路集中。谢谢 这对我来说太多代码了。我目前正在完全从事另一个项目...... ;-) 好的,感谢您的帮助,当我有时间我会完成它并让您知道它是如何进行的。 :) 您好 Günter,刚刚查看代码,我注意到第 60 行 _HomePageState() 下的 the final variable 'photos' must be initialised。我需要更改什么来解决这个问题? 您可能希望删除 final 修饰符,然后在 _HomePageState 的 initState() 中利用您的 fetch 和 parse 方法。【参考方案3】:

替换

class Photo 
  final int albumId;
  final int id;
  final String title;
  final String url;
  final String thumbnailUrl;

  Photo(this.albumId, this.id, this.title, this.url, this.thumbnailUrl);

  factory Photo.fromJson(Map<String, dynamic> json) 
    return Photo(
      albumId: json['albumId'] as int,
      id: json['id'] as int,
      title: json['title'] as String,
      url: json['url'] as String,
      thumbnailUrl: json['thumbnailUrl'] as String,
    );
  

与:

class Photo 
  final String albumId;
  final String id;
  final String title;
  final String url;
  final String thumbnailUrl;

  Photo(this.albumId, this.id, this.title, this.url, this.thumbnailUrl);

  factory Photo.fromJson(Map<String, dynamic> json) 
    return Photo(
      albumId: json['albumId'] as String,
      id: json['id'] as String,
      title: json['title'] as String,
      url: json['url'] as String,
      thumbnailUrl: json['thumbnailUrl'] as String,
    );
  

【讨论】:

以上是关于Flutter - 为 API 实现 JSON getter的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 如何根据 JSON API 的状态更改颜色

如何在 Flutter 中解析这个 JSON 数组?

Flutter - 使用 API 密钥

Flutter JSON 序列化 - 不生成 *.g.dart 文件

如何使用 Dio 在 Flutter 中调用 API?

Flutter 生成.g.dart文件