🪁

Supabaseのテーブルを2個表示したい。

2024/02/21に公開

💡Tips

Supabaseからテーブルの情報を2個取得して、画面に表示したいが、joinして、freezed使うのが無理そうだから、whenをネストして、RiverpodのStreamProviderを使ったらデータ取れた。

この記事の対象者

  • Supabaseの経験がある人
  • riverpod + freezedの経験がある人

とりあえずやってみた。

メモ書きのようなモンなので、コード全部載せてないです🙇
実は仕事で書いてる実験用のコードです。いいもんではないかも???

モデルを作る

モデル
// user用

class TUserState with _$TUserState {
  const factory TUserState({
    (0) int id,
    ('') String uuid,
    ('') String birthday,
    ('') String job_id,
    ('') String iconImagePath,
    ('') String user_name,
    ('') String profile,
    DateTime? created_at,
    DateTime? updated_at,
    (false) bool is_delete,
  }) = _TUserState;

  factory TUserState.fromJson(Map<String, dynamic> json) =>
      _$TUserStateFromJson(json);
}
// task用

class TTaskState with _$TTaskState {
  const factory TTaskState({
    (0) int id,
    ('') String status,
    ('') String task_name,
    DateTime? created_at,
    DateTime? updated_at,
    (false) bool is_delete,
    (0) int goal_id,
    (0) int user_id,
  }) = _TTaskState;

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

プロバイダーを作る。joinするクエリがあるのだ、freezedをネストさせて、tasksというプロパティからアクセするのが、できなかった???
for文で、ループすればデータ取れると思ったがうまくいかなかった....

StreamProviderを使ってみた
// user用

Stream<List<TUserState>> tUserList(TUserListRef ref) {
  final supabase = Supabase.instance.client;
  return supabase.from('t_user').stream(primaryKey: ['id']).limit(5).map(
        (event) => event.map(TUserState.fromJson).toList(),
      );
}
// task用

Stream<List<TTaskState>> tTaskList(TTaskListRef ref) {
  final supabase = Supabase.instance.client;
  return supabase.from('t_task').stream(primaryKey: ['id']).limit(5).map(
        (event) => event.map(TTaskState.fromJson).toList(),
      );
}

View側には、whenをネストして、Listの文法で、minと呼ばれるメソッドがあり、これを使わないとどちらか一方のListの数にあわせて、データを取得ができないので必要だった💦

whenをネストしてみた
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

import '../../core/theme/app_color.dart';
import '../../infrastructure/supabase_provider/t_task_provider.dart';
import '../../infrastructure/supabase_provider/t_user_provider.dart';

enum Selected { edit, delete }

class FeedPage extends HookConsumerWidget {
  const FeedPage({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final selectedMenu = useState<Selected>(Selected.edit);
    final tUser = ref.watch(tUserListProvider);
    final tTask = ref.watch(tTaskListProvider);
    return Scaffold(
      appBar: AppBar(),
      backgroundColor: AppColor.grey,
      floatingActionButton: FloatingActionButton(
        onPressed: () async {},
        child: const Icon(Icons.add),
      ),
      // tUserとtTaskをwhenでネストさせて表示。lengthはminを使う
      body: tUser.when(
        data: (tUser) => tTask.when(
          data: (tTask) => ListView.builder(
            itemCount: min(tUser.length, tTask.length),
            itemBuilder: (context, index) {
              return Card(
                child: ListTile(
                  leading: CircleAvatar(
                    backgroundColor: Colors.blue,
                    radius: 20,
                    backgroundImage: NetworkImage(tUser[index].iconImagePath),
                  ),
                  title: Text(tUser[index].user_name),
                  subtitle: Text(tTask[index].task_name),
                  trailing: PopupMenuButton<Selected>(
                    onSelected: (result) {
                      selectedMenu.value = result;
                    },
                    itemBuilder: (context) => <PopupMenuEntry<Selected>>[
                      const PopupMenuItem<Selected>(
                        value: Selected.edit,
                        child: Text('Edit'),
                      ),
                      const PopupMenuItem<Selected>(
                        value: Selected.delete,
                        child: Text('Delete'),
                      ),
                    ],
                  ),
                ),
              );
            },
          ),
          loading: () => const CircularProgressIndicator(),
          error: (error, stackTrace) => Text('Error: $error'),
        ),
        loading: () => const CircularProgressIndicator(),
        error: (error, stackTrace) => Text('Error: $error'),
      ),
    );
  }
}

データの取得に成功すると、View側に二つのテーブルの情報を表示することができた。世の中のSNSはREST APIなのだろうけど...

まとめ

Firestoreでもwhenをネストさせたことがあるが、あっちよりは簡単な気がしたが、いけてるコードではない気がします。他に良い方法があればいいんですけどね〜
joinして、View側に表示できないものか...

Discussion