Skip to content

Commit

Permalink
Merge pull request #24 from mqkotoo/session/11
Browse files Browse the repository at this point in the history
Session/11
  • Loading branch information
mqkotoo authored Oct 30, 2023
2 parents 3ac3c70 + fa2e29a commit b6380d7
Show file tree
Hide file tree
Showing 15 changed files with 361 additions and 178 deletions.
10 changes: 10 additions & 0 deletions doc/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,16 @@
## State

* `Service`からResultを取得し、取得した天気のデータを保持する
* ローディングの状態を通知する

### WeatherStateNotifier

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

### LoadingStateNotifier

* ローディングの状態を管理、`WeatherPage`に通知する

## View

* 天気の表示、エラーのダイアログを表示。 天気を取得するボタンを提供。
Expand All @@ -55,12 +60,14 @@
* アプリのメイン画面
* `Reload`ボタンを押して、`weatherStateNotifierProvider`をreadして、天気の取得処理を行う。
* `weatherStateNotifierProvider`で天気取得に失敗した場合は、エラーメッセージを表示する。
* `LoadingStateNotifier`のローディング状態をウォッチしてローディングを表示する。

### WeatherForecastPanel

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

## Providerの依存関係
```mermaid
flowchart TB
subgraph Arrows
Expand All @@ -82,12 +89,15 @@ flowchart TB
end
weatherStateNotifierProvider[["weatherStateNotifierProvider"]];
loadingStateNotifierProvider[["loadingStateNotifierProvider"]];
weatherServiceProvider[["weatherServiceProvider"]];
yumemiWeatherClientProvider[["yumemiWeatherClientProvider"]];
WeatherForecastPanel((WeatherForecastPanel));
WeatherPage((WeatherPage));
weatherStateNotifierProvider ==> WeatherForecastPanel;
loadingStateNotifierProvider ==> WeatherPage;
weatherStateNotifierProvider -.-> WeatherPage;
yumemiWeatherClientProvider ==> weatherServiceProvider;
```
7 changes: 5 additions & 2 deletions lib/service/weather_service.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:convert';

import 'package:flutter/foundation.dart';
import 'package:flutter_training/model/weather_forecast.dart';
import 'package:flutter_training/model/weather_request.dart';
import 'package:flutter_training/utils/api/result.dart';
Expand Down Expand Up @@ -30,10 +31,12 @@ class WeatherService {
/// If successful, the value is stored in [Success],
/// if unsuccessful, the error message is stored in [Failure].
Result<WeatherForecast, String> fetchWeather(WeatherRequest request) {
Future<Result<WeatherForecast, String>> fetchWeather(
WeatherRequest request,
) async {
try {
final jsonData = jsonEncode(request);
final resultJson = _client.fetchWeather(jsonData);
final resultJson = await compute(_client.syncFetchWeather, jsonData);
final weatherData = jsonDecode(resultJson) as Map<String, dynamic>;
final result = WeatherForecast.fromJson(weatherData);
return Success<WeatherForecast, String>(result);
Expand Down
1 change: 0 additions & 1 deletion lib/service/weather_service.g.dart

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

13 changes: 13 additions & 0 deletions lib/state/loading_state_notifier.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'loading_state_notifier.g.dart';

@riverpod
class LoadingStateNotifier extends _$LoadingStateNotifier {
@override
bool build() => false;

void show() => state = true;

void hide() => state = false;
}
29 changes: 29 additions & 0 deletions lib/state/loading_state_notifier.g.dart

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

10 changes: 7 additions & 3 deletions lib/state/weather_state_notifier.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter_training/model/weather_forecast.dart';
import 'package:flutter_training/model/weather_request.dart';
import 'package:flutter_training/service/weather_service.dart';
import 'package:flutter_training/state/loading_state_notifier.dart';
import 'package:flutter_training/utils/api/result.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

Expand All @@ -11,11 +12,14 @@ class WeatherStateNotifier extends _$WeatherStateNotifier {
@override
WeatherForecast? build() => null;

void getWeather({
Future<void> getWeather({
required WeatherRequest request,
required void Function(String error) onError,
}) {
return switch (ref.read(weatherServiceProvider).fetchWeather(request)) {
}) async {
ref.read(loadingStateNotifierProvider.notifier).show();
final result = await ref.read(weatherServiceProvider).fetchWeather(request);
ref.read(loadingStateNotifierProvider.notifier).hide();
return switch (result) {
Success(value: final value) => state = value,
Failure(exception: final error) => onError.call(error),
};
Expand Down
2 changes: 1 addition & 1 deletion lib/state/weather_state_notifier.g.dart

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

89 changes: 52 additions & 37 deletions lib/view/weather_view/weather_page.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_training/model/weather_request.dart';
import 'package:flutter_training/state/loading_state_notifier.dart';
import 'package:flutter_training/state/weather_state_notifier.dart';
import 'package:flutter_training/view/weather_view/component/weather_forecast_panel.dart';

Expand All @@ -25,50 +26,64 @@ class WeatherPage extends ConsumerWidget {
);
}

return Scaffold(
body: Center(
child: FractionallySizedBox(
widthFactor: 0.5,
child: Column(
children: [
const Spacer(),
const WeatherForecastPanel(),
Flexible(
child: Column(
children: [
const SizedBox(height: 80),
Row(
final isLoading = ref.watch(loadingStateNotifierProvider);

return Stack(
children: [
Scaffold(
body: Center(
child: FractionallySizedBox(
widthFactor: 0.5,
child: Column(
children: [
const Spacer(),
const WeatherForecastPanel(),
Flexible(
child: Column(
children: [
Expanded(
child: TextButton(
onPressed: () => Navigator.pop(context),
key: closeButtonKey,
child: const Text('Close'),
),
),
Expanded(
child: TextButton(
onPressed: () {
onReloaded(
WeatherRequest(
area: 'Nagoya',
date: DateTime.now(),
),
);
},
key: reloadButton,
child: const Text('Reload'),
),
const SizedBox(height: 80),
Row(
children: [
Expanded(
child: TextButton(
onPressed: () => Navigator.pop(context),
key: closeButtonKey,
child: const Text('Close'),
),
),
Expanded(
child: TextButton(
onPressed: () {
onReloaded(
WeatherRequest(
area: 'Nagoya',
date: DateTime.now(),
),
);
},
key: reloadButton,
child: const Text('Reload'),
),
),
],
),
],
),
],
),
),
],
),
],
),
),
),
),
// ローディングを表示
if (isLoading)
const ColoredBox(
color: Colors.black54,
child: Center(
child: CircularProgressIndicator(),
),
),
],
);
}
}
Expand Down
20 changes: 11 additions & 9 deletions test/service/weather_service_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ void main() {
container.dispose();
});

test('success case', () {
when(mockClient.fetchWeather(any)).thenReturn(validJsonData);
test('success case', () async {
when(mockClient.syncFetchWeather(any)).thenReturn(validJsonData);

final result = container.read(weatherServiceProvider).fetchWeather(request);
final result =
await container.read(weatherServiceProvider).fetchWeather(request);

expect(
result,
Expand All @@ -55,12 +56,12 @@ void main() {
});

group('failure case', () {
test('invalidParameter error is thrown', () {
when(mockClient.fetchWeather(any))
test('invalidParameter error is thrown', () async {
when(mockClient.syncFetchWeather(any))
.thenThrow(YumemiWeatherError.invalidParameter);

final result =
container.read(weatherServiceProvider).fetchWeather(request);
await container.read(weatherServiceProvider).fetchWeather(request);

expect(
result,
Expand All @@ -72,11 +73,12 @@ void main() {
);
});

test('unknown error is thrown', () {
when(mockClient.fetchWeather(any)).thenThrow(YumemiWeatherError.unknown);
test('unknown error is thrown', () async {
when(mockClient.syncFetchWeather(any))
.thenThrow(YumemiWeatherError.unknown);

final result =
container.read(weatherServiceProvider).fetchWeather(request);
await container.read(weatherServiceProvider).fetchWeather(request);

expect(
result,
Expand Down
3 changes: 0 additions & 3 deletions test/service/weather_service_test.mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ class MockYumemiWeather extends _i1.Mock implements _i2.YumemiWeather {
returnValue: '',
returnValueForMissingStub: '',
) as String);

@override
String fetchThrowsWeather(String? area) => (super.noSuchMethod(
Invocation.method(
Expand All @@ -40,7 +39,6 @@ class MockYumemiWeather extends _i1.Mock implements _i2.YumemiWeather {
returnValue: '',
returnValueForMissingStub: '',
) as String);

@override
String fetchWeather(String? jsonString) => (super.noSuchMethod(
Invocation.method(
Expand All @@ -50,7 +48,6 @@ class MockYumemiWeather extends _i1.Mock implements _i2.YumemiWeather {
returnValue: '',
returnValueForMissingStub: '',
) as String);

@override
String syncFetchWeather(String? jsonString) => (super.noSuchMethod(
Invocation.method(
Expand Down
Loading

0 comments on commit b6380d7

Please sign in to comment.