【Flutter x Supabase】SNS系のアプリで自分の投稿だけを取得する方法




# 本編

こんにちわ、いせりゅーです。
今回は、個人的に注目しているSupabaseについてです。
色々な記事で、データを取得・削除・追加などのやり方は公式ドキュメントや技術記事に載っておりましたが、自分の投稿だけを取得する方法が探しても見つからなかったので、記事にさせていただきました。

<b>公式サイト</b>

https://supabase.com/


# 今回話すこと
- SNS系のアプリで自分の投稿だけを取得する方法

それ以外は割愛させていただきます。

# Postのデータ

今回は以下のデータのModelにしました。

:::note warn
注意
現時点でのアプリのデータのModelは開発段階のものとなっています。
今後も改善予定です。
:::

| 型 | スネークケース | 変数名 | 説明 |
| --- | --- | --- | --- |
| int | id | id | ID |
| String | food_image | foodImage | 食べ物の画像 |
| String | food_name | foodName | 食べ物の名前 |
| String | restaurant | restaurant | レストラン名 |
| String | comment | comment | コメント |
| TimeStamp | created_at | createdAt | 作成時間 |
| float8 | lat | lat | 緯度 |
| float8 | lng | lng | 経度 |
| int | heart | heart | いいね数 |


# やりたいこと

- タイムライン画面では、すべての投稿を取得したい。
- プロフィール画面では、自分の投稿を取得したい。

:::note warn
注意
現時点でのアプリのUIは開発段階のものとなっています。
今後も改善予定です。
:::



| タイムライン画面 | プロフィール画面 | 
| --- | --- |
| <img width="100%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672609/c222fab1-ae33-68a9-f686-9f4292ed8663.png"> | <img width="100%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672609/ed26a4d2-5df4-c551-d54e-3461bb669c20.png">
 | 





# 実装していく

### 全部の投稿を取得したい場合


```dart
Supabase.instance.client.from('posts').stream(primaryKey: ['id']);
```

### 自分の投稿のみを取得したい場合

自分の投稿（ID）のみを取得したい場合は、`stream`の後に`eq()`で指定したデータのみを取得するようにします。

```dart
final supabase = Supabase.instance.client;
// 自分のアカウントのIDを取得
final userId = supabase.auth.currentUser?.id;

supabase.from('posts').stream(primaryKey: ['id']).eq('user_id', userId);
```




# コードの例

```dart:my_profile.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:food_gram_app/component/app_list_view.dart';
import 'package:food_gram_app/screen/edit/edit_screen.dart';
import 'package:food_gram_app/screen/my_profile/my_profile_view_model.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

class MyProfileScreen extends ConsumerWidget {
  const MyProfileScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(myProfileViewModelProvider());
    final supabase = Supabase.instance.client;
    final user = supabase.auth.currentUser?.id;
    final stream =
        supabase.from('posts').stream(primaryKey: ['id']).eq('user_id', user);
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(backgroundColor: Colors.white),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Padding(
            padding: EdgeInsets.symmetric(horizontal: 20),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Row(
                  children: [
                    CircleAvatar(
                      backgroundColor: Colors.white,
                      backgroundImage: AssetImage(state.image),
                      radius: 50,
                    ),
                    Spacer(),
                    Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text(
                          '13',
                          style: TextStyle(
                            fontSize: 22,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                        Text(
                          '投稿',
                          style: TextStyle(
                            fontSize: 15,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ],
                    ),
                    SizedBox(width: 10),
                  ],
                ),
                SizedBox(height: 20),
                Text(
                  state.name,
                  style: TextStyle(
                    fontSize: 20,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                Text(
                  '@${state.userName}',
                  style: TextStyle(
                    fontSize: 18,
                  ),
                ),
                Text(
                  state.selfIntroduce,
                  style: TextStyle(
                    fontSize: 16,
                  ),
                ),
              ],
            ),
          ),
          const SizedBox(height: 20),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              ElevatedButton(
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.white,
                ),
                onPressed: () {
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => const EditScreen(),
                    ),
                  ).then((_) {
                    ref
                        .read(myProfileViewModelProvider().notifier)
                        .getProfile();
                  });
                },
                child: const Text(
                  'プロフィールを編集',
                  style: TextStyle(
                    fontWeight: FontWeight.bold,
                    color: Colors.black,
                  ),
                ),
              ),
              ElevatedButton(
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.white,
                ),
                onPressed: () {
                  //TODO シェアする
                },
                child: const Text(
                  'プロフィールをシェア',
                  style: TextStyle(
                    fontWeight: FontWeight.bold,
                    color: Colors.black,
                  ),
                ),
              ),
            ],
          ),
          const SizedBox(height: 20),
          AppListView(stream: stream),
        ],
      ),
    );
  }
}

```

```dart:app_list_view.dart
import 'package:flutter/material.dart';
import 'package:food_gram_app/model/post.dart';
import 'package:food_gram_app/screen/detail/detail_post_screen.dart';

class AppListView extends StatelessWidget {
  const AppListView({
    required this.stream,
    super.key,
  });

  final Stream<List<Map<String, dynamic>>>? stream;

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: StreamBuilder<List<Map<String, dynamic>>>(
        stream: stream,
        builder: (context, snapshot) {
          if (!snapshot.hasData) {
            return const Center(
              child: CircularProgressIndicator(),
            );
          }
          final data = snapshot.data!;
          return GridView.builder(
            itemCount: data.length,
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 3,
              crossAxisSpacing: 1,
              mainAxisSpacing: 1,
            ),
            itemBuilder: (context, index) {
              return GestureDetector(
                onTap: () {
                  final post = Post(
                    id: int.parse(data[index]['id'].toString()),
                    foodImage: data[index]['food_image'],
                    foodName: data[index]['food_name'],
                    restaurant: data[index]['restaurant'],
                    comment: data[index]['comment'],
                    createdAt: DateTime.parse(data[index]['created_at']),
                    lat: double.parse(data[index]['lat'].toString()),
                    lng: double.parse(data[index]['lng'].toString()),
                    heart: int.parse(data[index]['heart'].toString()),
                  );
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => DetailPostScreen(
                        post: post,
                      ),
                    ),
                  );
                },
                child: Container(
                  width: MediaQuery.of(context).size.width / 3,
                  height: MediaQuery.of(context).size.width / 3,
                  color: Colors.blue,
                  //TODO あとでNetworkImageに変更する
                  child: Image.network(data[index]['food_image']),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

```

# Supabaseを使用する上で参考になる記事

https://zenn.dev/flutteruniv_dev/articles/crud_with_supabase

https://zenn.dev/flutteruniv_dev/articles/da265483f0073c

https://supabase.com/docs/guides/getting-started/tutorials/with-flutter

https://supabase.com/docs/guides/getting-started/quickstarts/flutter