Skip to content

Commit

Permalink
Merge pull request #21 from mqkotoo/session/8
Browse files Browse the repository at this point in the history
Session/8
  • Loading branch information
mqkotoo authored Sep 27, 2023
2 parents 80dadaf + 177b379 commit 1e9e41f
Show file tree
Hide file tree
Showing 15 changed files with 485 additions and 99 deletions.
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
# flutter_training
# ゆめみ Flutter研修課題

A new Flutter project.
テンプレート

- https://github.com/yumemi-inc/flutter-training-template

## 環境構築

*リポジトリをクローン

```
git clone https://github.com/mqkotoo/flutter_training.git
```

* 作業ディレクトリを変更する

```
cd flutter_training
```

* fvmに指定されたバージョンのFlutterをインストールする

```
fvm install
```

* 依存パッケージをインストールする

```
fvm flutter pub get
```

* ビルドラン

```
fvm flutter run
```
7 changes: 7 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,10 @@ include: package:blendthink_lints/recommended.yaml
linter:
rules:
sort_pub_dependencies: false

analyzer:
errors:
# riverpod_generatorの影響でg.dartの末尾に不要な指定がつくので記載
duplicate_ignore: ignore
plugins:
- custom_lint
93 changes: 93 additions & 0 deletions doc/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
## アーキテクチャ

今回のプロジェクトではあえて、いわゆるアーキテクチャというような大掛かりな設計はせずに、[`WeatherStateNotifier`](../lib/state/weather_state_notifier.dart)
という、天気の取得操作、結果を管理するプロバイダーを作成し、Viewレイヤとやりとりするだけのシンプルな設計にしました。

### ルール

* Stateレイヤでなんらかの状態を操作して保管する。
* Viewレイヤから`ref.read`してStateの操作、`ref.watch`してStateの表示を行う。
* Stateレイヤのロジックが肥大する場合はServiceレイヤに移動させる。

上記のルールに基づいて開発を進めました。

### モチベーション

* 今回のような規模が小さいプロジェクトに、たくさんレイヤーがあるアーキテクチャを採用すると無駄なレイヤーが増え、複雑化する。
* 無理やり一定のアーキテクチャの型にはめると、個人的にはまだアーキテクチャの知識が浅いので、実際の必要性を感じることなく形だけのアーキテクチャになる可能性がある。
* 簡潔でシンプルでわかりやすい。(主観になりますが。。)

## Model

* 天気情報やリクエストを送るモデルを定義、変換処理等を定義

### WeatherCondition

* 天気のステータスを定義、取得した天気に応じてイメージを返すextensionを定義

### WeatherForecast

* 天気の情報、最低気温、最高気温、日にちを定義
* データの変換処理を定義

### WeatherRequest

* リクエストを送るクラスを定義

## Service

* 天気を取得して、[Result](../lib/utils/api/result.dart)型に変換するメソッドを提供する

## State

* `Service`からResultを取得し、取得した天気のデータを保持する

### WeatherStateNotifier

* 天気の取得が成功したらそのデータを格納し、失敗した場合は、`onError`関数を引数で受け取る。

## View

* 天気の表示、エラーのダイアログを表示。 天気を取得するボタンを提供。

### WeatherPage

* アプリのメイン画面
* `Reload`ボタンを押して、`weatherStateNotifierProvider`をreadして、天気の取得処理を行う。
* `weatherStateNotifierProvider`で天気取得に失敗した場合は、エラーメッセージを表示する。

### WeatherForecastPanel

* 天気の情報、気温等を表示しているコンポネント。
* `weatherStateNotifierProvider`をwatchして、天気の状態を表示する。

```mermaid
flowchart TB
subgraph Arrows
direction LR
start1[ ] -..->|read| stop1[ ]
style start1 height:0px;
style stop1 height:0px;
start2[ ] --->|listen| stop2[ ]
style start2 height:0px;
style stop2 height:0px;
start3[ ] ===>|watch| stop3[ ]
style start3 height:0px;
style stop3 height:0px;
end
subgraph Type
direction TB
ConsumerWidget((widget));
Provider[[provider]];
end
weatherStateNotifierProvider[["weatherStateNotifierProvider"]];
weatherServiceProvider[["weatherServiceProvider"]];
yumemiWeatherClientProvider[["yumemiWeatherClientProvider"]];
WeatherForecastPanel((WeatherForecastPanel));
WeatherPage((WeatherPage));
weatherStateNotifierProvider ==> WeatherForecastPanel;
weatherStateNotifierProvider -.-> WeatherPage;
yumemiWeatherClientProvider ==> weatherServiceProvider;
```
8 changes: 7 additions & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_training/view/launch_page.dart';

void main() {
runApp(const MainApp());
WidgetsFlutterBinding.ensureInitialized();
runApp(
const ProviderScope(
child: MainApp(),
),
);
}

class MainApp extends StatelessWidget {
Expand Down
14 changes: 7 additions & 7 deletions lib/model/weather_data.dart → lib/model/weather_forecast.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import 'package:flutter_training/model/weather_condition.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'weather_data.freezed.dart';
part 'weather_forecast.freezed.dart';

part 'weather_data.g.dart';
part 'weather_forecast.g.dart';

@freezed
class WeatherData with _$WeatherData {
const factory WeatherData({
class WeatherForecast with _$WeatherForecast {
const factory WeatherForecast({
required WeatherCondition weatherCondition,
required int maxTemperature,
required int minTemperature,
required DateTime date,
}) = _WeatherData;
}) = _WeatherForecast;

factory WeatherData.fromJson(Map<String, dynamic> json) =>
_$WeatherDataFromJson(json);
factory WeatherForecast.fromJson(Map<String, dynamic> json) =>
_$WeatherForecastFromJson(json);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// 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 'weather_data.dart';
part of 'weather_forecast.dart';

// **************************************************************************
// FreezedGenerator
Expand All @@ -14,28 +14,28 @@ 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');

WeatherData _$WeatherDataFromJson(Map<String, dynamic> json) {
return _WeatherData.fromJson(json);
WeatherForecast _$WeatherForecastFromJson(Map<String, dynamic> json) {
return _WeatherForecast.fromJson(json);
}

/// @nodoc
mixin _$WeatherData {
mixin _$WeatherForecast {
WeatherCondition get weatherCondition => throw _privateConstructorUsedError;
int get maxTemperature => throw _privateConstructorUsedError;
int get minTemperature => throw _privateConstructorUsedError;
DateTime get date => throw _privateConstructorUsedError;

Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$WeatherDataCopyWith<WeatherData> get copyWith =>
$WeatherForecastCopyWith<WeatherForecast> get copyWith =>
throw _privateConstructorUsedError;
}

/// @nodoc
abstract class $WeatherDataCopyWith<$Res> {
factory $WeatherDataCopyWith(
WeatherData value, $Res Function(WeatherData) then) =
_$WeatherDataCopyWithImpl<$Res, WeatherData>;
abstract class $WeatherForecastCopyWith<$Res> {
factory $WeatherForecastCopyWith(
WeatherForecast value, $Res Function(WeatherForecast) then) =
_$WeatherForecastCopyWithImpl<$Res, WeatherForecast>;
@useResult
$Res call(
{WeatherCondition weatherCondition,
Expand All @@ -45,9 +45,9 @@ abstract class $WeatherDataCopyWith<$Res> {
}

/// @nodoc
class _$WeatherDataCopyWithImpl<$Res, $Val extends WeatherData>
implements $WeatherDataCopyWith<$Res> {
_$WeatherDataCopyWithImpl(this._value, this._then);
class _$WeatherForecastCopyWithImpl<$Res, $Val extends WeatherForecast>
implements $WeatherForecastCopyWith<$Res> {
_$WeatherForecastCopyWithImpl(this._value, this._then);

// ignore: unused_field
final $Val _value;
Expand Down Expand Up @@ -84,11 +84,11 @@ class _$WeatherDataCopyWithImpl<$Res, $Val extends WeatherData>
}

/// @nodoc
abstract class _$$_WeatherDataCopyWith<$Res>
implements $WeatherDataCopyWith<$Res> {
factory _$$_WeatherDataCopyWith(
_$_WeatherData value, $Res Function(_$_WeatherData) then) =
__$$_WeatherDataCopyWithImpl<$Res>;
abstract class _$$_WeatherForecastCopyWith<$Res>
implements $WeatherForecastCopyWith<$Res> {
factory _$$_WeatherForecastCopyWith(
_$_WeatherForecast value, $Res Function(_$_WeatherForecast) then) =
__$$_WeatherForecastCopyWithImpl<$Res>;
@override
@useResult
$Res call(
Expand All @@ -99,11 +99,11 @@ abstract class _$$_WeatherDataCopyWith<$Res>
}

/// @nodoc
class __$$_WeatherDataCopyWithImpl<$Res>
extends _$WeatherDataCopyWithImpl<$Res, _$_WeatherData>
implements _$$_WeatherDataCopyWith<$Res> {
__$$_WeatherDataCopyWithImpl(
_$_WeatherData _value, $Res Function(_$_WeatherData) _then)
class __$$_WeatherForecastCopyWithImpl<$Res>
extends _$WeatherForecastCopyWithImpl<$Res, _$_WeatherForecast>
implements _$$_WeatherForecastCopyWith<$Res> {
__$$_WeatherForecastCopyWithImpl(
_$_WeatherForecast _value, $Res Function(_$_WeatherForecast) _then)
: super(_value, _then);

@pragma('vm:prefer-inline')
Expand All @@ -114,7 +114,7 @@ class __$$_WeatherDataCopyWithImpl<$Res>
Object? minTemperature = null,
Object? date = null,
}) {
return _then(_$_WeatherData(
return _then(_$_WeatherForecast(
weatherCondition: null == weatherCondition
? _value.weatherCondition
: weatherCondition // ignore: cast_nullable_to_non_nullable
Expand All @@ -137,15 +137,15 @@ class __$$_WeatherDataCopyWithImpl<$Res>

/// @nodoc
@JsonSerializable()
class _$_WeatherData implements _WeatherData {
const _$_WeatherData(
class _$_WeatherForecast implements _WeatherForecast {
const _$_WeatherForecast(
{required this.weatherCondition,
required this.maxTemperature,
required this.minTemperature,
required this.date});

factory _$_WeatherData.fromJson(Map<String, dynamic> json) =>
_$$_WeatherDataFromJson(json);
factory _$_WeatherForecast.fromJson(Map<String, dynamic> json) =>
_$$_WeatherForecastFromJson(json);

@override
final WeatherCondition weatherCondition;
Expand All @@ -158,14 +158,14 @@ class _$_WeatherData implements _WeatherData {

@override
String toString() {
return 'WeatherData(weatherCondition: $weatherCondition, maxTemperature: $maxTemperature, minTemperature: $minTemperature, date: $date)';
return 'WeatherForecast(weatherCondition: $weatherCondition, maxTemperature: $maxTemperature, minTemperature: $minTemperature, date: $date)';
}

@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_WeatherData &&
other is _$_WeatherForecast &&
(identical(other.weatherCondition, weatherCondition) ||
other.weatherCondition == weatherCondition) &&
(identical(other.maxTemperature, maxTemperature) ||
Expand All @@ -183,26 +183,26 @@ class _$_WeatherData implements _WeatherData {
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_WeatherDataCopyWith<_$_WeatherData> get copyWith =>
__$$_WeatherDataCopyWithImpl<_$_WeatherData>(this, _$identity);
_$$_WeatherForecastCopyWith<_$_WeatherForecast> get copyWith =>
__$$_WeatherForecastCopyWithImpl<_$_WeatherForecast>(this, _$identity);

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

abstract class _WeatherData implements WeatherData {
const factory _WeatherData(
abstract class _WeatherForecast implements WeatherForecast {
const factory _WeatherForecast(
{required final WeatherCondition weatherCondition,
required final int maxTemperature,
required final int minTemperature,
required final DateTime date}) = _$_WeatherData;
required final DateTime date}) = _$_WeatherForecast;

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

@override
WeatherCondition get weatherCondition;
Expand All @@ -214,6 +214,6 @@ abstract class _WeatherData implements WeatherData {
DateTime get date;
@override
@JsonKey(ignore: true)
_$$_WeatherDataCopyWith<_$_WeatherData> get copyWith =>
_$$_WeatherForecastCopyWith<_$_WeatherForecast> get copyWith =>
throw _privateConstructorUsedError;
}
Loading

0 comments on commit 1e9e41f

Please sign in to comment.