(Async)NotifierProvider + Freezdでリレーションのデータを取得
対象者
- 中級者むけと思うのでわかってるの前提で書きます。
- (Async)NotifierProviderわかる
- Freezedわかる
- Supabaseわかる
プロジェクトの説明
前回こんな記事を書きました。
StatefulWidgetで書いてる(^^;;
Riverpod + Freezedで書いたらどうなるのか?
リレーションでデータ取得できるのか?、いやできるでしょ。世の中のREST APIはできてるのだから
Entityを作成
ネストしたモデルクラス作成しましょう。呪文みたいなコード書いてますが、toJson, fromJson書いてなくて、自動生成してもらってるだけだと思ってください。
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';
part 'follow.freezed.dart';
part 'follow.g.dart';
// flutter pub run build_runner watch --delete-conflicting-outputs
class Relationships with _$Relationships {
const factory Relationships({
(0) int followingId,
(FollowState(id: 0, name: '')) FollowState followings,
}) = _Relationships;
factory Relationships.fromJson(Map<String, Object?> json)
=> _$RelationshipsFromJson(json);
}
class FollowState with _$FollowState {
const factory FollowState({
(0) int id,
('') String name,
}) = _FollowState;
factory FollowState.fromJson(Map<String, dynamic> json) => _$FollowStateFromJson(json);
}
状態を扱うクラスを作成
Stateクラスなるものですね。FutureProvider使えばいらないと思いますが、(Async)NotifierProviderを使う人が最近多いのかなと思って、使うことにしました。直接ロジック書くの好きではないですが、今回は書きますか。
buildメソッドのデータ型は、FutureOr<List<Relationships>>にしてします。戻り値に返すのは、データを取得してくれるメソッドです。これが初期値になります。
import 'package:flutter/cupertino.dart';
import 'package:follower_app/entity/follow/follow.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
part 'follow_notifier.g.dart';
class FollowNotifier extends _$FollowNotifier {
FutureOr<List<Relationships>> build() async {
return fetchRelationships();
}
Future<List<Relationships>> fetchRelationships() async {
try {
List<Relationships> relationships = [];
final response = await Supabase.instance.client
.from('relationships')
.select('following_id, followings(id, name)');
for (var data in response) {
relationships.add(Relationships.fromJson(data));
}
return relationships;
} catch (e) {
debugPrint('Error: $e');
}
return [];
}
}
Viewのコード作成
Type: AsyncValue<List<Relationships>>のデータ型の変数を watch してViewに表示をしてみましょう。今は、whenじゃなくて、switchに変わったのかな。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:follower_app/usecase/follow_notifier.dart';
class HomePage extends ConsumerWidget {
const HomePage({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final relationships = ref.watch(followNotifierProvider);
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.redAccent,
title: const Text('AsyncFollow'),
),
body: switch (relationships) {
AsyncData(:final value) => ListView.builder(
itemCount: value.length,
itemBuilder: (context, index) {
final relationship = value[index];
return ListTile(
title: Text(relationship.followings.name),
subtitle: Text('id: ${relationship.followings.id}'),
);
},
),
AsyncError(:final error) => const Center(child: Text('エラーが発生しました')),
_ => const Center(child: CircularProgressIndicator()),
});
}
}
感想
もし画面を更新するロジックが必要な場面が出てきたときは、(Async)NotifierProvider使うと思う。参考になる情報がないので、どうやってこんなロジック考えるのだろうかと毎回思うのだけど、オブジェクト思考がわかれば多分できると思う。
(Async)NotifierProvider, (Stream)NotifierProviderを使いたい場面もあると思うので、そういったシナリオに使うと良さそうです。
🫛知識
StatefulWidget
は、setState
だけで状態を管理できる。画面の更新ができる。しかし使いすぎると、画面が固まるとか、画面遷移したときに、動きが遅くなる? 悩みが出てきました。コードの記述量も増えるとファイルが肥大化してしまう。
[コードの説明]
class Example extends StatefulWidget {
const Example({super.key});
State<Example> createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
// State classの中に状態変数とロジックを書く
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Example'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
// 状態変数を変更する処理
});
},
child: const Icon(Icons.add),
)
);
}
}
ロジック、状態、画面は分離させたいので、Riverpodを使うのですが、よくあるコードでないと中々できない😅
setState
の代わりに、state
という状態変数を使う。今回は使ってないけど。ちなみに、 void型
の時は、build
メソッドの中は、何も書かなくて良い。戻り値を返さないのでね。
[コードの説明]
class FollowNotifier extends _$FollowNotifier {
[データ型] build() async {
return データ型と同じ初期値。例: 1, []; "";
}
// 処理を記述
// stateを更新するメソッド
void setState() {
// Setter/Getterが内部で動いている
state = データ型と同じ値;
}
}
Discussion