LoginSignup
9
4

【Flutter】freezedでネストさせるとき

Last updated at Posted at 2022-03-30

freezedでネスト構造のAPIレスポンスをパースするときに行ったことを共有します。

前提

以下、こちらのOpenWeatherAPIの例で書いてます。

以下でAPIを叩くと

https://api.openweathermap.org/data/2.5/weather?q=Tokyo&appid=${appId}

こんなレスポンスがもらえます。

{
    "coord": {
        "lon": 139.6917,
        "lat": 35.6895
    },
    "weather": [
        {
            "id": 800,
            "main": "Clear",
            "description": "clear sky",
            "icon": "01n"
        }
    ],
    "base": "stations",
    "main": {
        "temp": 287.65,
        "feels_like": 287.3,
        "temp_min": 285.4,
        "temp_max": 288.51,
        "pressure": 1017,
        "humidity": 82
    },
    "visibility": 10000,
    "wind": {
        "speed": 6.69,
        "deg": 200
    },
    "clouds": {
        "all": 0
    },
    "dt": 1648659355,
    "sys": {
        "type": 2,
        "id": 2038398,
        "country": "JP",
        "sunrise": 1648672190,
        "sunset": 1648717259
    },
    "timezone": 32400,
    "id": 1850144,
    "name": "Tokyo",
    "cod": 200
}

このレスポンスから、描画に必要な情報だけを取り出します。
こちらの記事をベースにしていたので、参考にしているので、View層で描画に必要な情報は以下になります。

今回は以下のWeatherクラスを作りました。

import 'package:equatable/equatable.dart';

class Weather extends Equatable {
  const Weather({
    required this.cityName,
    required this.main,
    required this.description,
    required this.iconCode,
    required this.temperature,
    required this.pressure,
    required this.humidity,
  });

  final String cityName;
  final String main;
  final String description;
  final String iconCode;
  final double temperature;
  final int pressure;
  final int humidity;

  @override
  List<Object?> get props => [
    cityName,
    main,
    description,
    iconCode,
    temperature,
    pressure,
    humidity,
  ];
}

freezedでのネストの構造(本題)

ネスト構造を持たせる場合は、「@JsonSerializable(explicitToJson: true)」の設定が必要でした。
https://github.com/rrousselGit/freezed/issues/86

なお以下には、Entityとして中身を取り出すメソッド(toEntity)も定義しています。freezedで独自メソッドを作る場合は、元のabstract classにimplementsするようにし、private constructorを追加します。

これで、以下のfreezedクラスを使って、レスポンス→WeatherApiResponseの形にパースを行い(fromJson)、WeatherApiResponseからWeatherの情報が取り出せる(toEntity)ようになります!

import 'package:hogehoge/entities/models/weather.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';

part 'weather_api.freezed.dart';
part 'weather_api.g.dart';

@freezed
abstract class WeatherApi with _$WeatherApi {
  @JsonSerializable(explicitToJson: true)
    const WeatherApi._();
  const factory WeatherApi.request({
    required List<WeatherInfo>? weather,
    required MainInfo? main,
    required String? name,
  }) = WeatherApiRequest;
  const factory WeatherApi.response({
    required List<WeatherInfo>? weather,
    required MainInfo? main,
    required String? name,
  }) = WeatherApiResponse;

  factory WeatherApi.fromJson(Map<String, dynamic> json) => _$WeatherApiFromJson(json);
  Weather toEntity() => Weather(
      cityName: name ?? "",
      main: weather?[0].main ?? "",
      description: weather?[0].description ?? "",
      iconCode: weather?[0].icon ?? "",
      temperature: main?.temperature ?? 0,
      pressure: main?.pressure ?? 0,
      humidity: main?.humidity ?? 0,
  );
}

@freezed
abstract class WeatherInfo with _$WeatherInfo {
  @JsonSerializable(explicitToJson: true)
  const factory WeatherInfo({
    required String? main,
    required String? description,
    required String? icon,
  }) = _WeatherInfo;
  factory WeatherInfo.fromJson(Map<String, dynamic> json) => _$WeatherInfoFromJson(json);

}

@freezed
abstract class MainInfo with _$MainInfo {
  const factory MainInfo({
    @required @JsonKey(name: 'temp_max') double? temperature,
    required int? pressure,
    required int? humidity,
  }) = _MainInfo;
  factory MainInfo.fromJson(Map<String, dynamic> json) => _$MainInfoFromJson(json);
}

こちらを、APIを叩いた時のレスポンスに対して、以下実行することで「WeatherApiResponse」が取り出せます。

WeatherApiResponse.fromJson(json.decode(response.body)

実行結果

呼び出しコードは別途載せますが、無事にWeatherApiResponseにデータを格納することができました。
この結果に対して、freezedの抽象クラスに用意したtoEntityでWeatherが取り出せます。

WeatherApi.response(
    weather: [WeatherInfo(
        main: Clouds, 
        description: broken clouds, 
        icon: 04d)], 
    main: MainInfo(
        temperature: 290.68, 
        pressure: 1014, 
        humidity: 73), 
    name: Tokyo)

呼び出し時のコード(参考)

  Future<Either<ErrorApi, WeatherApiResponse>> getCurrentWeather(String cityName) async {
    http.Client client = http.Client();

    // クエリパラメータを定義します。
    final queryParameters = {
      'q': cityName,
      'appid': apiKey,
    };

    try {
      // クエリパラメータがある場合は、Urihttpを使うしかないようです
      final uri = Uri.https(baseAuth, "/data/2.5/weather", queryParameters);

      // final response = await client.get(uri, headers: headers);
      final response = await client.get(uri);
      if (200 <= response.statusCode && response.statusCode <= 300) {
        return Right(WeatherApiResponse.fromJson(json.decode(response.body)));
      } else {
        return Left(ErrorApi.fromJson(json.decode(response.body)));
      }
    } on SocketException catch (e){
      throw const SocketException('No Internet connection');
    } on Exception catch (e) {
      throw OtherException(e.toString());
    } finally {
      client.close();
    }
  }
9
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4