Supabaseでリレーションを使って他のテーブルのデータを取得
Tips💡
Supabaseでリレーションを使って他のテーブルのデータを取得して、Viewに表示する実験を最近やってます。綺麗なコードではないが実験に成功したので、記録を残そうと記事を書きます。本当はモデルクラスを作ったりして、データ型をdynamic
にしないようにしないといけないんですけどね(−_−;)
ダミーのテーブルのデータを作成しておく
create table
followings (
id bigint primary key generated always as identity,
name text
);
create table
followers (
id bigint primary key generated always as identity,
name text
);
create table
relationships (
id bigint primary key generated always as identity,
following_id bigint references followings (id),
follower_id bigint references followers (id)
);
-- master data for followings
insert into followings (name) values ('Alice');
insert into followings (name) values ('Bob');
insert into followings (name) values ('Charlie');
-- master data for followers
insert into followers (name) values ('Alice');
insert into followers (name) values ('Bob');
insert into followers (name) values ('Charlie');
-- relationships
insert into relationships (following_id, follower_id) values (1, 1);
insert into relationships (following_id, follower_id) values (1, 2);
insert into relationships (following_id, follower_id) values (1, 3);
中間テーブルが今回あるのですが、こちらをデータ取得のテーブルとして使います。便利なツールを使うとER図を作れます。
ER図
仕組みは、フォローする人、される人のテーブルの間にリレーションがあって、このテーブルにidを保存して、その idを参照すると、他のテーブルのデータを取得することができます。認証もするアプリだろうから、本当はuuidとか入れるのでしょうけど(^_^;)
[サンプルコード]
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:follower_app/view/sign_in_page.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Future<List<dynamic>?> relationships() async {
final response = await Supabase.instance.client
.from('relationships')
.select('following_id, followings(id, name)');
print(response);
return response as List<dynamic>;
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
onPressed: () async {
// ログアウトするボタン.
await Supabase.instance.client.auth.signOut();
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => const SigninPage()));
},
icon: const Icon(Icons.logout)),
],
title: const Text('Home'),
),
body: FutureBuilder(
future: relationships(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return const Center(child: Text('エラーが発生しました'));
}
final data = snapshot.data as List<dynamic>;
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(data[index]['followings']['name']),
subtitle: Text(data[index]['followings']['id'].toString()),
);
},
);
},
),
);
}
}
[screen shot]
すいません。今回は同じカラムのデータを3回取ってきてるみたいです😅
最後に
SQLを使っていると、リレーションの知識を求めれるようになってきました。やることは、「参照」すること。何をするのか。idを使って、他のテーブルのデータを取得できる。
海外の動画でも参考になりそうなものがありました。綺麗なコードに整えてから仕事では使います。このままでは使いずらい💦
追加情報
Null型のデータをMap<String, dynamic>型として扱うので、モデルクラスを作成するときは、null checkが必要でした💦
AIによると
エラーメッセージから判断すると、Null型のデータをMap<String, dynamic>型として扱おうとしているため、エラーが発生しているようです。これは、取得したデータがnullであるか、または取得したデータの一部がnullで、それをマップとして扱おうとしたときに発生します。 モデルクラスを使用してデータを取得する場合、JSONからモデルクラスへの変換中にnullチェックを行うことが重要です。これにより、nullの値が存在する場合でも適切に処理することができます。 例えば、以下のようにfromJsonメソッドを修正することで、nullチェックを行うことができます。
こんな感じで作ってください。
class Relationships {
final int followingId;
final Following followings;
Relationships({required this.followingId, required this.followings});
factory Relationships.fromJson(Map<String, dynamic> json) {
return Relationships(
followingId: json['following_id'] ?? 0,
followings: Following.fromJson(json['followings'] ?? {}),
);
}
}
class Following {
final int id;
final String name;
Following({required this.id, required this.name});
factory Following.fromJson(Map<String, dynamic> json) {
return Following(
id: json['id'] ?? 0,
name: json['name'] ?? '',
);
}
}
View側のコードも修正。さっきと表示は変わらないですけどね😅
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:follower_app/view/sign_in_page.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import '../entity/relationships.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Future<List<dynamic>?> relationships() async {
final response = await Supabase.instance.client
.from('relationships')
.select('following_id, followings(id, name)');
print(response);
return response as List<dynamic>;
}
// class Relationships data type
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) {
print(e);
}
return [];
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
onPressed: () async {
// ログアウトするボタン.
await Supabase.instance.client.auth.signOut();
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => const SigninPage()));
},
icon: const Icon(Icons.logout)),
],
title: const Text('Home2222'),
),
// body: FutureBuilder(
// future: relationships(),
// builder: (context, snapshot) {
// if (snapshot.connectionState == ConnectionState.waiting) {
// return const Center(child: CircularProgressIndicator());
// }
// if (snapshot.hasError) {
// return const Center(child: Text('エラーが発生しました'));
// }
// final data = snapshot.data as List<dynamic>;
// return ListView.builder(
// itemCount: data.length,
// itemBuilder: (context, index) {
// return ListTile(
// title: Text(data[index]['followings']['name']),
// subtitle: Text(data[index]['followings']['id'].toString()),
// );
// },
// );
// },
// ),
body: FutureBuilder(
future: fetchRelationships(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return const Center(child: Text('エラーが発生しました'));
}
final data = snapshot.data as List<Relationships>;
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(data[index].followings.name),
subtitle: Text(data[index].followings.id.toString()),
);
},
);
},
),
);
}
}
Discussion