@JsonKeyはどう使う?
Freezedのよくわからない機能?
最近、Freezedを使っているのですけど、みんな書き方が違って@なんとか
を使ってる人が多かったりして、自分はFreezedを有効に活用できてないなと思って、今回はいつもと違う書き方で、モデルクラスを作ってみました。
🔤公式を翻訳してみた
JsonKeyアノテーションはどうですか?
コンストラクタのパラメータに渡されたすべてのデコレータは、生成されたプロパティにも「コピーペースト」されます。
そのため、次のように書くことができます:
class Example with _$Example {
factory Example((name: 'my_property') String myProperty) = _Example;
factory Example.fromJson(Map<String, dynamic> json) => _$ExampleFromJson(json);
}
JsonSerializableアノテーションはどうですか?
JsonSerializableアノテーションをコンストラクタの上に置くことで、@JsonSerializableアノテーションを渡すことができます:
class Example with _$Example {
(explicitToJson: true)
factory Example((name: 'my_property') SomeOtherClass myProperty) = _Example;
factory Example.fromJson(Map<String, dynamic> json) => _$ExampleFromJson(json);
}
全てのクラスに対してカスタムjson_serializableフラグを定義したい場合(explicit_to_jsonやany_mapなど)、ここで説明されているように、build.yamlファイルを通して行うことができる。
デコレータのセクションも参照してください。
日本語の情報もあるので、リンク貼っておきます。
🎁まずはパッケージを追加しておく
name: freezed_app
description: A new Flutter project.
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: "none" # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
environment:
sdk: ">=3.0.5 <4.0.0"
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
cupertino_icons: ^1.0.2
dio: ^5.3.0
flutter:
sdk: flutter
flutter_riverpod: ^2.3.6
freezed_annotation: ^2.2.0
json_annotation: ^4.8.1
dev_dependencies:
build_runner: ^2.4.6
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^2.0.0
flutter_test:
sdk: flutter
freezed: ^2.3.5
json_serializable: ^6.7.1
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
⚙️モデルクラスを作る
今回の使い方だと、@JsonKey(name: )
がやってくれいることは、APIのオブジェクトのプロパティの名前を中に書いて、変数は別名(エイリアス)にしてるだけです。
例えば、APIのデータが、user_nameと書いてあったら、userNameにしてるだけ。
@JsonKey(name: 'user_name') String userNameといった感じですね。
// ignore_for_file: invalid_annotation_target
import 'package:freezed_annotation/freezed_annotation.dart';
part 'post.freezed.dart';
part 'post.g.dart';
class Post with _$Post {
const factory Post({
(0) (name: 'id') int postId,// APIのIDが数字なので、int型にしてる
('') (name: 'title') String postTitle,
}) = _Post;
factory Post.fromJson(Map<String, dynamic> json) =>
_$PostFromJson(json);
}
コマンドを打って必要なファイルを自動生成する
dart run build_runner build
APIと通信するロジックを作る
APIと通信するためのロジックをdio
を使って、おこなうレポジトリクラスを作ろうと思います。ベースURLなるものを定義すれば、dioでは、.getを使うときは、idを指定するパスを書くだけで良かったりする。httpにはこんな機能はなかったような?
import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_app/domain/post.dart';
// レポジトリをインスタンス化するグローバルなProvider
final dioClientProvider = Provider((ref) => DioClient(ref: ref));
// dioをインスタンス化するグローバルなProvider
final dioProvider = Provider((ref) => Dio(
// ベースURLを設定していれば、getメソッドの引数にはパスのみを指定できる
BaseOptions(
baseUrl: 'https://jsonplaceholder.typicode.com/todos',
// タイムアウトを5秒に設定する
connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 5),
),
));
// APIと通信するレポジトリクラス
class DioClient {
Ref ref;
DioClient({required this.ref});
// jsonplaceholderからタスクの一覧を取得する
Future<List<Post>> getPost() async {
// ベースURLがあれば、 .getの中は、('') でも良い。本来はこの中に、idなどのパスを指定する
final response = await ref.read(dioProvider).get('');
final posts = (response.data as List)
.map((e) => Post.fromJson(e as Map<String, dynamic>))
.toList();
return posts;
}
}
レポジトリクラスをUIにデータを表示するのに使うFutureProviderで呼び出す。
// DioClientを使うFutureProviderを作成する
// DioClientを使うFutureProviderを作成する
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_app/application/feature1/dio_client.dart';
import 'package:freezed_app/domain/post.dart';
// DioClientを使うFutureProviderを作成する
final postsProvider = FutureProvider<List<Post>>((ref) async {
// DioClientを取得する
final dioClient = ref.watch(dioClientProvider);
// DioClientのgetTasksメソッドを呼び出す
final posts = await dioClient.getPost();
return posts;
});
🛜UIにAPIから取得したデータを表示
今回実験で作ったコードですので、reflistenは機能していません。これは自作したAPIで実験したかったので、使ってたんですけど、reander.comのAPIが使えなくなってたので、タイムアウトの実験ができませんでした。loadingのところに、ダイアログを書くと、10秒後に実行される現象は起きました?
タイムアウトはしてないはずなんですけどね。。。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_app/application/feature1/dio_provider.dart';
class PostList extends ConsumerWidget {
const PostList({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final postAsyncValue = ref.watch(postsProvider);
// タイムアウトしたら、ref.listenを使ってダイアログを出す
ref.listen(postsProvider, (postAsyncValue, previousValue) {
postAsyncValue!.when(
data: (tasks) {
Navigator.pop(context);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stackTrace) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('エラー'),
content: Text(error.toString()),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
);
},
);
},
);
});
return Scaffold(
appBar: AppBar(
title: const Text('HTTP GET'),
),
body: postAsyncValue.when(
data: (posts) {
return ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
final post = posts[index];
return ListTile(
title: Text(post.postTitle),
subtitle: Text(post.postId.toString()),
);
},
);
},
loading: () => const Center(child: Text('読み込み中........')),
error: (error, stackTrace) => Center(child: Text(error.toString())),
),
);
}
}
main.dartでimportしてビルドすれば、実行できます。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_app/applicaton/feature2/post_list.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const PostList(),
);
}
}
実行結果
まとめ
Freezedは他にもたくさんの機能があって、まだ理解できていません。また何か新しい書き方を覚えたら記事にしようと思います。
Discussion