😇

(Async)NotifierProvider + Freezdでリレーションのデータを取得

2024/06/01に公開

対象者

  • 中級者むけと思うのでわかってるの前提で書きます。
    • (Async)NotifierProviderわかる
    • Freezedわかる
    • Supabaseわかる

プロジェクトの説明

前回こんな記事を書きました。
https://zenn.dev/joo_hashi/articles/049664215435de

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