Skip to content
This repository has been archived by the owner on Dec 6, 2023. It is now read-only.

ライブラリを用いたシリアル化 #21

Merged
merged 13 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
# https://pub.dev/packages/yumemi_lints
include: package:yumemi_lints/flutter/3.16.0/recommended.yaml

analyzer:
exclude:
- "**/*.g.dart"
- "**/*.freezed.dart"
blendthink marked this conversation as resolved.
Show resolved Hide resolved
errors:
# https://pub.dev/packages/freezed#install
invalid_annotation_target: ignore
9 changes: 9 additions & 0 deletions build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
targets:
$default:
builders:
# https://github.com/google/json_serializable.dart/tree/master/json_serializable#build-configuration
blendthink marked this conversation as resolved.
Show resolved Hide resolved
json_serializable:
options:
field_rename: snake
# json のデシリアライズ時に発生する Exception を CheckedFromJsonException にまとめる
checked: true
53 changes: 53 additions & 0 deletions lib/date_time_converter.dart
blendthink marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:intl/intl.dart';

/// yumemi_weather で用いられる日時形式に変換する JsonConverter
///
/// 形式例
/// - 2020-04-01T12:00:00+09:00
/// 参照
/// - https://yumemi-inc.github.io/flutter-training-template/file-___home_runner_work_flutter-training-template_flutter-training-template_packages_yumemi_weather_lib_src_yumemi_weather_base/YumemiWeather/fetchWeather.html
class DateTimeConverter implements JsonConverter<DateTime, String> {
const DateTimeConverter();

// DateTime#toIso8601String() はマイクロ秒まで出力されるため用いない
// タイムゾーンは intl では未実装のため自前で追加する
// see: https://api.flutter.dev/flutter/intl/DateFormat-class.html
static final DateFormat dateFormatter = DateFormat("yyyy-MM-dd'T'HH:mm:ss");

static final NumberFormat timeFormatter = NumberFormat('00');

@override
DateTime fromJson(String json) {
// DateFormat#parse() では UTC とローカルのタイムゾーン以外対応できない
// DateTime.parse() はタイムゾーンを考慮した上で UTC に変更する
return DateTime.parse(json).toLocal();
}

@override
String toJson(DateTime object) {
final dateTimeStr = dateFormatter.format(object);
final timeZoneStr = _toTimeZoneString(object.timeZoneOffset);
return '$dateTimeStr$timeZoneStr';
}

String _toTimeZoneString(Duration offset) {
final inMinutes = offset.inMinutes.abs();

if (inMinutes == 0) {
return 'Z';
}

final String sign;
if (offset.isNegative) {
sign = '-';
} else {
sign = '+';
}
blendthink marked this conversation as resolved.
Show resolved Hide resolved

final hours = timeFormatter.format(inMinutes ~/ Duration.minutesPerHour);
final minutes = timeFormatter.format(inMinutes % Duration.minutesPerHour);

return '$sign$hours:$minutes';
}
}
25 changes: 0 additions & 25 deletions lib/forecast.dart

This file was deleted.

2 changes: 1 addition & 1 deletion lib/forecast_view.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_training/forecast.dart';
import 'package:flutter_training/model/forecast.dart';

class ForecastView extends StatelessWidget {
const ForecastView({
Expand Down
16 changes: 9 additions & 7 deletions lib/main_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_training/forecast.dart';
import 'package:flutter_training/forecast_view.dart';
import 'package:flutter_training/model/fetch_weather_request.dart';
import 'package:flutter_training/model/forecast.dart';
import 'package:flutter_training/yumemi_weather_error.dart';
import 'package:yumemi_weather/yumemi_weather.dart';

Expand All @@ -19,15 +20,16 @@ class _MainScreenState extends State<MainScreen> {
Forecast? _forecast;

void _fetchForecast() {
const request = {
'area': 'tokyo',
'date': '2023-11-22T00:00:00+09:00',
};
final requestString = jsonEncode(request);
final request = FetchWeatherRequest(
area: 'tokyo',
date: DateTime.now(),
);
final requestString = jsonEncode(request.toJson());
warahiko marked this conversation as resolved.
Show resolved Hide resolved

final Forecast newForecast;
try {
newForecast = Forecast.from(_yumemiWeather.fetchWeather(requestString));
final responseString = _yumemiWeather.fetchWeather(requestString);
newForecast = Forecast.fromJsonString(responseString);
} on YumemiWeatherError catch (e) {
unawaited(_showErrorDialog(e.toMessage()));
return;
Expand Down
16 changes: 16 additions & 0 deletions lib/model/fetch_weather_request.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:flutter_training/date_time_converter.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'fetch_weather_request.freezed.dart';
part 'fetch_weather_request.g.dart';

@freezed
class FetchWeatherRequest with _$FetchWeatherRequest {
const factory FetchWeatherRequest({
required String area,
@DateTimeConverter() required DateTime date,
}) = _FetchWeatherRequest;

factory FetchWeatherRequest.fromJson(Map<String, dynamic> json) =>
_$FetchWeatherRequestFromJson(json);
}
175 changes: 175 additions & 0 deletions lib/model/fetch_weather_request.freezed.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark

part of 'fetch_weather_request.dart';

// **************************************************************************
// FreezedGenerator
// **************************************************************************

T _$identity<T>(T value) => value;

final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');

FetchWeatherRequest _$FetchWeatherRequestFromJson(Map<String, dynamic> json) {
return _FetchWeatherRequest.fromJson(json);
}

/// @nodoc
mixin _$FetchWeatherRequest {
String get area => throw _privateConstructorUsedError;
@DateTimeConverter()
DateTime get date => throw _privateConstructorUsedError;

Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$FetchWeatherRequestCopyWith<FetchWeatherRequest> get copyWith =>
throw _privateConstructorUsedError;
}

/// @nodoc
abstract class $FetchWeatherRequestCopyWith<$Res> {
factory $FetchWeatherRequestCopyWith(
FetchWeatherRequest value, $Res Function(FetchWeatherRequest) then) =
_$FetchWeatherRequestCopyWithImpl<$Res, FetchWeatherRequest>;
@useResult
$Res call({String area, @DateTimeConverter() DateTime date});
}

/// @nodoc
class _$FetchWeatherRequestCopyWithImpl<$Res, $Val extends FetchWeatherRequest>
implements $FetchWeatherRequestCopyWith<$Res> {
_$FetchWeatherRequestCopyWithImpl(this._value, this._then);

// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;

@pragma('vm:prefer-inline')
@override
$Res call({
Object? area = null,
Object? date = null,
}) {
return _then(_value.copyWith(
area: null == area
? _value.area
: area // ignore: cast_nullable_to_non_nullable
as String,
date: null == date
? _value.date
: date // ignore: cast_nullable_to_non_nullable
as DateTime,
) as $Val);
}
}

/// @nodoc
abstract class _$$FetchWeatherRequestImplCopyWith<$Res>
implements $FetchWeatherRequestCopyWith<$Res> {
factory _$$FetchWeatherRequestImplCopyWith(_$FetchWeatherRequestImpl value,
$Res Function(_$FetchWeatherRequestImpl) then) =
__$$FetchWeatherRequestImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String area, @DateTimeConverter() DateTime date});
}

/// @nodoc
class __$$FetchWeatherRequestImplCopyWithImpl<$Res>
extends _$FetchWeatherRequestCopyWithImpl<$Res, _$FetchWeatherRequestImpl>
implements _$$FetchWeatherRequestImplCopyWith<$Res> {
__$$FetchWeatherRequestImplCopyWithImpl(_$FetchWeatherRequestImpl _value,
$Res Function(_$FetchWeatherRequestImpl) _then)
: super(_value, _then);

@pragma('vm:prefer-inline')
@override
$Res call({
Object? area = null,
Object? date = null,
}) {
return _then(_$FetchWeatherRequestImpl(
area: null == area
? _value.area
: area // ignore: cast_nullable_to_non_nullable
as String,
date: null == date
? _value.date
: date // ignore: cast_nullable_to_non_nullable
as DateTime,
));
}
}

/// @nodoc
@JsonSerializable()
class _$FetchWeatherRequestImpl implements _FetchWeatherRequest {
const _$FetchWeatherRequestImpl(
{required this.area, @DateTimeConverter() required this.date});

factory _$FetchWeatherRequestImpl.fromJson(Map<String, dynamic> json) =>
_$$FetchWeatherRequestImplFromJson(json);

@override
final String area;
@override
@DateTimeConverter()
final DateTime date;

@override
String toString() {
return 'FetchWeatherRequest(area: $area, date: $date)';
}

@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$FetchWeatherRequestImpl &&
(identical(other.area, area) || other.area == area) &&
(identical(other.date, date) || other.date == date));
}

@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, area, date);

@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$FetchWeatherRequestImplCopyWith<_$FetchWeatherRequestImpl> get copyWith =>
__$$FetchWeatherRequestImplCopyWithImpl<_$FetchWeatherRequestImpl>(
this, _$identity);

@override
Map<String, dynamic> toJson() {
return _$$FetchWeatherRequestImplToJson(
this,
);
}
}

abstract class _FetchWeatherRequest implements FetchWeatherRequest {
const factory _FetchWeatherRequest(
{required final String area,
@DateTimeConverter() required final DateTime date}) =
_$FetchWeatherRequestImpl;

factory _FetchWeatherRequest.fromJson(Map<String, dynamic> json) =
_$FetchWeatherRequestImpl.fromJson;

@override
String get area;
@override
@DateTimeConverter()
DateTime get date;
@override
@JsonKey(ignore: true)
_$$FetchWeatherRequestImplCopyWith<_$FetchWeatherRequestImpl> get copyWith =>
throw _privateConstructorUsedError;
}
29 changes: 29 additions & 0 deletions lib/model/fetch_weather_request.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions lib/model/forecast.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'dart:convert';

import 'package:flutter_training/model/weather.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'forecast.freezed.dart';
part 'forecast.g.dart';

@freezed
class Forecast with _$Forecast {
const factory Forecast({
@JsonKey(name: 'weather_condition') required Weather weather,
required int maxTemperature,
required int minTemperature,
}) = _Forecast;

factory Forecast.fromJson(Map<String, dynamic> json) =>
_$ForecastFromJson(json);

factory Forecast.fromJsonString(String json) {
final decoded = jsonDecode(json) as Map<String, dynamic>?;
if (decoded == null) {
throw Exception('Invalid format: $json.');
}

return Forecast.fromJson(decoded);
}
}
Loading
Loading