Murray O'Brien
Murray O'Brien

Reputation: 47

Parse JSON to List in Dart

I'm trying to parse some complex JSON from a weather API, and display it in a list view. I've used quickType to generate a model for me, but cannot seem to parse the data to a list. I'm fairly certain that the main part that is wrong is my logic in the parsing of the data in the service file, as the data from the API comes in a complex structure that I don't know how to parse. Because of the API data, its split up into 2 maps or lists? Hours and meta. And therefore there being an extra class. Please help me.

Here's the service/ parse file:

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

class Service {
  static const lat = '-33.7506';
  static const lng = '18.4401';
  static const params =
      'swellDirection,windSpeed,windDirection,wavePeriod,waveHeight,airTemperature';

  static Future<List<Hour>> getConditions() async {
    try {
      final response = await http.get(
          Uri.encodeFull(
              'https://api.stormglass.io/v2/weather/point?lat=$lat&lng=$lng&params=$params&start=2020-12-11&end=2020-12-12'),
          headers: {
            HttpHeaders.authorizationHeader:
                'exampleapi'
          });

      if (200 == response.statusCode) {
        final conditions = conditionsFromJson(response.body);
        print(response.body);
        return conditions.hours;
      }
    } catch (e) {
      print('Not working');
      return List<Hour>();
    }
  }
}

Heres my Main file:

    import 'package:flutter/material.dart';
import 'package:moreapi_practise/weather_model.dart';
import 'Service.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'My API Practice'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Hour> _conditions;
  bool _loading;

  @override
  void initState() {
    super.initState();
    _loading = true;
    Service.getConditions().then((conditions) {
      _conditions = conditions;
      _loading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(_loading ? 'Loading...' : 'Conditions'),
      ),
      body: Container(child: ListView.builder(itemBuilder: (context, index) {
        Hour condition = _conditions[index];
        return ListTile(
          title: Text('${condition.airTemperature}'),
        );
      })),
    );
  }
}

And then my model:

   // To parse this JSON data, do
//
//     final conditions = conditionsFromJson(jsonString);

import 'dart:convert';

Conditions conditionsFromJson(String str) => Conditions.fromJson(json.decode(str));

String conditionsToJson(Conditions data) => json.encode(data.toJson());

class Conditions {
    Conditions({
        this.hours,
        this.meta,
    });

    List<Hour> hours;
    Meta meta;

    factory Conditions.fromJson(Map<String, dynamic> json) => Conditions(
        hours: List<Hour>.from(json["hours"].map((x) => Hour.fromJson(x))),
        meta: Meta.fromJson(json["meta"]),
    );

    Map<String, dynamic> toJson() => {
        "hours": List<dynamic>.from(hours.map((x) => x.toJson())),
        "meta": meta.toJson(),
    };
}

class Hour {
    Hour({
        this.airTemperature,
        this.swellDirection,
        this.time,
        this.waveHeight,
        this.wavePeriod,
        this.windDirection,
        this.windSpeed,
    });

    AirTemperature airTemperature;
    SwellDirection swellDirection;
    DateTime time;
    SwellDirection waveHeight;
    SwellDirection wavePeriod;
    SwellDirection windDirection;
    SwellDirection windSpeed;

    factory Hour.fromJson(Map<String, dynamic> json) => Hour(
        airTemperature: AirTemperature.fromJson(json["airTemperature"]),
        swellDirection: SwellDirection.fromJson(json["swellDirection"]),
        time: DateTime.parse(json["time"]),
        waveHeight: SwellDirection.fromJson(json["waveHeight"]),
        wavePeriod: SwellDirection.fromJson(json["wavePeriod"]),
        windDirection: SwellDirection.fromJson(json["windDirection"]),
        windSpeed: SwellDirection.fromJson(json["windSpeed"]),
    );

    Map<String, dynamic> toJson() => {
        "airTemperature": airTemperature.toJson(),
        "swellDirection": swellDirection.toJson(),
        "time": time.toIso8601String(),
        "waveHeight": waveHeight.toJson(),
        "wavePeriod": wavePeriod.toJson(),
        "windDirection": windDirection.toJson(),
        "windSpeed": windSpeed.toJson(),
    };
}

class AirTemperature {
    AirTemperature({
        this.noaa,
        this.sg,
    });

    double noaa;
    double sg;

    factory AirTemperature.fromJson(Map<String, dynamic> json) => AirTemperature(
        noaa: json["noaa"].toDouble(),
        sg: json["sg"].toDouble(),
    );

    Map<String, dynamic> toJson() => {
        "noaa": noaa,
        "sg": sg,
    };
}

class SwellDirection {
    SwellDirection({
        this.icon,
        this.meteo,
        this.noaa,
        this.sg,
    });

    double icon;
    double meteo;
    double noaa;
    double sg;

    factory SwellDirection.fromJson(Map<String, dynamic> json) => SwellDirection(
        icon: json["icon"].toDouble(),
        meteo: json["meteo"] == null ? null : json["meteo"].toDouble(),
        noaa: json["noaa"].toDouble(),
        sg: json["sg"].toDouble(),
    );

    Map<String, dynamic> toJson() => {
        "icon": icon,
        "meteo": meteo == null ? null : meteo,
        "noaa": noaa,
        "sg": sg,
    };
}

class Meta {
    Meta({
        this.cost,
        this.dailyQuota,
        this.end,
        this.lat,
        this.lng,
        this.params,
        this.requestCount,
        this.start,
    });

    int cost;
    int dailyQuota;
    String end;
    double lat;
    double lng;
    List<String> params;
    int requestCount;
    String start;

    factory Meta.fromJson(Map<String, dynamic> json) => Meta(
        cost: json["cost"],
        dailyQuota: json["dailyQuota"],
        end: json["end"],
        lat: json["lat"].toDouble(),
        lng: json["lng"].toDouble(),
        params: List<String>.from(json["params"].map((x) => x)),
        requestCount: json["requestCount"],
        start: json["start"],
    );

    Map<String, dynamic> toJson() => {
        "cost": cost,
        "dailyQuota": dailyQuota,
        "end": end,
        "lat": lat,
        "lng": lng,
        "params": List<dynamic>.from(params.map((x) => x)),
        "requestCount": requestCount,
        "start": start,
    };
}

Upvotes: 1

Views: 378

Answers (2)

0x4b50
0x4b50

Reputation: 679

Try json['hours’] as List<dynamic>. Then you can pass that to a parsing method and iterate over the elements.

List<Hour> hours = parseHourList(json['hours'] as List<dynamic>);

// ...

List<Hour> parseHourList(List<dynamic> jsonList) {
  final List<Hour> list = [];
  for(dynamic element in jsonList) {
    final Hour hour = Hour.fromJson(element as Map<String, dynamic>);
    list.add(hour);
  }
  return list;
}

Upvotes: 0

bluenile
bluenile

Reputation: 6029

Please check the working code for your main.dart. The issue was that you were calling Service.getConditions in initState. Service.getConditions is a future and you were not waiting for the Future to complete before loading the data in ListView. The code below will show you one of the ways of how you can wait for the Future to complete and then load the data. In ListView you were showing condition.airTemperature so I made a bit of change there and now showing condition.airTemperature.noaa& condition.airTemperature.sg.

import 'package:flutter/material.dart';
import 'package:moreapi_practise/weather_model.dart';
import 'Service.dart';


void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'My API Practice'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Hour> _conditions;
  bool _loading;
  Future myFuture;

  Future _getService() async {
    await Service.getConditions().then((conditions) {
      setState(() {
        _conditions = conditions;
        _loading = false;
      });
    });
  }

  @override
  void initState() {
    super.initState();
    _loading = true;
    myFuture = _getService();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(_loading ? 'Loading...' : 'Conditions'),
      ),
      body: _loading
          ? const Center(
              child: CircularProgressIndicator(),
            )
          : Container(
              child: ListView.builder(
                itemCount: _conditions.length,
                itemBuilder: (context, index) {
                  final Hour condition = _conditions[index];
                  return ListTile(
                    title: Text(
                        '${condition.airTemperature.noaa} ${condition.airTemperature.sg}'),
                  );
                },
              ),
            ),
    );
  }
}

Upvotes: 1

Related Questions