🛂

maybeWhenとは?

2024/01/21に公開
2

Overview

https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValueX/maybeWhen.html
RiverpodのmaybeWhenなるものを使う機会があったので調べてることにした。公式の解説だと...


一部のケースを意図的に処理せずに、 AsyncValueの状態を大文字と小文字を切り替えます。

AsyncValue が処理されないケースにあった場合は、 を返しますorElse。

デフォルトでは、Ref.refresh またはRef.invalidateによってトリガーされた場合、 whenは「読み込み」状態をスキップします (ただし、 Ref.watchによってトリガーされた場合は読み込み状態をスキップしません)。

AsyncValueが同時に複数の状態になる場合(プロバイダーをリロードするときや、有効なデータの後にエラーを発行するときなど)、 / /を呼び出すかどうかをカスタマイズするためのさまざまなフラグが提供される とき:loadingerrordata

skipLoadingOnReload(デフォルトでは false) Ref.watchloading が原因でプロバイダーが再構築された場合に を呼び出すかどうかをカスタマイズします。その状況では、when は 前の状態で/のいずれかを呼び出そうとします。errordata

skipLoadingOnRefresh(デフォルトで true) は、Ref.refresh またはRef.invalidate がloading 原因でプロバイダーが再構築される場合に を呼び出すかどうかを制御します。その状況では、when は 前の状態で/のいずれかを呼び出そうとします。errordata

skipError(デフォルトでは false) は、前の値dataが利用可能かどうかではなく、呼び出すかどうかを決定します。error

summary

どんなものなのか使ってみて考えてみたが、エラーの処理を分けるユースケースで使われているようだ。

こちらは参考にならなかった。Providerの内容だった。
https://codewithandrea.com/videos/flutter-state-management-setstate-freezed-state-notifier-provider/

context.watch<CreateProfileState>() を呼び出すことで、状態が変化したときにウィジェットが確実に再構築されるようにします。 次に、Freezed によって生成された .maybeWhen() メソッドを使用して、必要な isLoading 変数と errorText 変数を抽出します。 この例では、noError、error、および読み込み状態を特定のウィジェットに直接マップできないため、これを行う必要があります。 代わりに、状態が UI に 1 対 1 でマップされている場合は、state.when(...) を使用して、状態ごとに異なるウィジェットを返すことができます。

私が書いてるコードだとこんな感じのがあります。

The body might complete normally, causing 'null' to be returned, but the return type, 'FutureOr<Weather>', is a potentially non-nullable type.
Try adding either a return or a throw statement at the end.dartbody_might_complete_normally

本文は正常に完了し、「null」が返される可能性がありますが、戻り値の型 'FutureOr<Weather>' は潜在的に null 非許容型です。
最後に return または throw ステートメントを追加してみてください。

AsyncNotifierを使うときに、returnthrowをつけろと怒られる!
この場合に、よくmaybeWhenを使いますね。あってるか疑問ですが😅

import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weather_api/model/weather.dart';
import 'package:weather_api/repository/weather_repository.dart';
part 'weather_view_model.g.dart';


class WeatherViewModel extends _$WeatherViewModel {
  
  FutureOr<Weather> build() {
    return getWeatherData();
  }

  Future<Weather> getWeatherData() async {
    state = const AsyncValue.loading();
    state = await AsyncValue.guard(() async {
      return await ref.read(weatherRepositoryImplProvider).getWeatherData();
    });
    return state.maybeWhen(
      data: (data) => data,
      orElse: () => throw Exception('API Error!'));
  }
}	

デバッグしながら、動作検証したらnullのデータが入ってきたらエラー処理を実行してましたね。僕がプロバイダーの書き方を間違えてただけなんですけどね笑

// 自動保管で書いてしまった💣

Dio dio(DioRef ref) => Dio(ref));

正しい書き方はこっち⬇️

import 'package:dio/dio.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'dio_provider.g.dart';


Dio dio(DioRef ref) {
  return Dio();
}

thoughts

自分のやってるオンラインサロンで大学生の子APIからデータが取ってこれなくて困ってるらしく教えてたんですけど、JSONがネストしてるのから、どうやってデータとれば良いのだろうと悩まされました😇
でも良い経験になりましたね。慣れれば、調べなくてもすぐにモデル作ったり、OpenAPIで使える。

こちらが今回作成した完成品

もしerror logを出したい時はどうする?
errorパラメーターを追加します。

return state.maybeWhen(
      data: (data) => data,
      error: (error, stackTrace) => throw Exception('例外を投げます $error'),
      orElse: () => throw Exception('TaskState Error!'),
    );

Discussion

taiseitaisei

context.watch<CreateProfileState>() を呼び出すことで、

これってProviderパッケージの使い方ではないでしょうか?
Riverpodの説明としては不適切な気がします!

AsyncNotifierを使うときに、returnかthrowをつけろと怒られる!
この場合に、よくmaybeWhenを使いますね。あってるか疑問ですが😅

こちらはAsyncNotifier使用時にreturnかthrowをつけろ警告されるわけでなく、関数の戻り値を非nullのWeather型を返すように指定しているので警告されるものかと思います。

JboyHashimotoJboyHashimoto

taiseさんコメントありがとうございます!

これってProviderパッケージの使い方ではないでしょうか?
Riverpodの説明としては不適切な気がします!

ここは修正しました!

こちらはAsyncNotifier使用時にreturnかthrowをつけろ警告されるわけでなく、関数の戻り値を非nullのWeather型を返すように指定しているので警告されるものかと思います。

Freezedをnull許容しなくてもreturnかthrowは返さないといけなかったですね。

import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weather_api/model/weather.dart';
import 'package:weather_api/repository/weather_repository.dart';
part 'weather_view_model.g.dart';

// AsyncNotifierで、非同期処理を行う。FutureProviderと同じことができる。

class WeatherViewModel extends _$WeatherViewModel {
  
  FutureOr<Weather> build() {
    return getWeatherData();
  }
  // リポジトリを呼び出して、View側にデータを返す
  Future<Weather> getWeatherData() async {
    state = const AsyncValue.loading();
    state = await AsyncValue.guard(() async {
      return await ref.read(weatherRepositoryImplProvider).getWeatherData();
    });
    // 値がnullの場合は、例外を投げる
    // return state.maybeWhen(
    //   data: (data) => data,
    //   orElse: () => throw Exception('API Error!'));
    // 非nullのWeatherデータを返す
    return state.asData!.value;
  }
}

モデル:

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';

part 'weather.freezed.dart';
part 'weather.g.dart';


class Weather with _$Weather {
  factory Weather({
    required double latitude,
    required double longitude,
    required double generationtime_ms,
    required int utc_offset_seconds,
    required String timezone,
    required String timezone_abbreviation,
    required double elevation,
    required HourlyUnits hourly_units,
    required Hourly hourly,
  }) = _Weather;

  factory Weather.fromJson(Map<String, dynamic> json) =>
      _$WeatherFromJson(json);
}


class HourlyUnits with _$HourlyUnits {
  factory HourlyUnits({
    required String time,
    required String temperature_2m,
  }) = _HourlyUnits;

  factory HourlyUnits.fromJson(Map<String, dynamic> json) =>
      _$HourlyUnitsFromJson(json);
}


class Hourly with _$Hourly {
  factory Hourly({
    required List<String> time,
    required List<double> temperature_2m,
  }) = _Hourly;

  factory Hourly.fromJson(Map<String, dynamic> json) => _$HourlyFromJson(json);
}

// // JSONの全体の構造を表すクラス
// @freezed
// class Weather with _$Weather {
//   factory Weather({
//     double? latitude,
//     double? longitude,
//     double? generationtime_ms,
//     int? utc_offset_seconds,
//     String? timezone,
//     String? timezone_abbreviation,
//     double? elevation,// int -> double
//     HourlyUnits? hourly_units,
//     Hourly? hourly,
//   }) = _Weather;

//   factory Weather.fromJson(Map<String, dynamic> json) =>
//       _$WeatherFromJson(json);
// }

// // hourly_unitsの構造を表すクラス hourly_units: {}
// @freezed
// class HourlyUnits with _$HourlyUnits {
//   factory HourlyUnits({
//     String? time,// hourly_units: {"time": []"}
//     String? temperature_2m,// hourly_units: {"temperature_2m": []"}
//   }) = _HourlyUnits;

//   factory HourlyUnits.fromJson(Map<String, dynamic> json) =>
//       _$HourlyUnitsFromJson(json);
// }

// // hourlyの構造を表すクラス hourly: {}
// @freezed
// class Hourly with _$Hourly {
//   factory Hourly({
//     List<String>? time,// hourly: {"time": []"}
//     List<double>? temperature_2m,// hourly: {"temperature_2m": []"}
//   }) = _Hourly;

//   factory Hourly.fromJson(Map<String, dynamic> json) => _$HourlyFromJson(json);
// }