简介

在本文中,我将向您展示如何使用 Flutter 和 Supabase 作为后端来构建应用程序。 Supabase 是使用 Postgres 的 Firebase 替代品,所以这是不同的东西,甚至是惊人的。 Postgres 也是一个关系数据库,它也是开源的和强大的工具。

我们将学习并使用 Flutter 和 Supbase 构建一个简单的杂货应用程序。

我不会一步一步地设置 Flutter,因为我构建它的方式使用了遵循 MVVM 样式的 Stacked 架构,所以我将向您展示如何在 Flutter 应用程序中使用 Dart 编写 Supabase 代码。

您可以在FilledStacks了解有关堆叠架构的更多信息:D

我的 SupaGrocery 存储库将在本教程结束时共享。所以你可以继续下载它。

演示

SupaGrocery 演示

数据库设计

但在我们开始其他一切之前,我们先来看看我们的数据库设计。请参阅下面的附图。

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s---XG-zVbA---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev- to-uploads.s3.amazonaws.com/uploads/articles/suilro2gdbj4vei269l4.png)

对于我们简单的杂货店应用程序,我们只需要其中的 4 个表。

  • app_users:这是我们将存储用户的表,它将与 supabase auth 用户具有相同的主 ID。我不能只使用users表,因为它不能被公开阅读,所以我必须创建这个表。

  • groceries:每个用户的所有购物清单都将存储在此表中。

  • products:用户创建的所有产品都将存储在此表中。

  • grocery_products:这是我们将产品与杂货店联系起来的地方。这就是我们所说的数据透视表。

关系

在关系数据库中,表关系是很常见的事情,也是我在关系数据库中最喜欢的。

这两个是最常见的关系:

  • 一对一

  • 一对多

  • 多对多(数据透视表)

我们的app_users表与我们创建的两个表(即productsgroceries)具有一对多关系,因为用户可以拥有许多杂货清单,也可以在该杂货清单中拥有许多产品。

然后对于我们的groceries表,我们将created_by列作为外键,以便链接到app_users表,然后将其标识为我们应用程序中用户购物清单的一部分。

created_by列作为外键的products表也是如此。

然后对于我们的数据透视表,这是一个多对多关系,因为一个杂货清单可以有许多产品,一个产品可以属于许多杂货清单。

Supabase 设置

创建您的第一个 Supabase 帐户!前往https://supabase.io/这是他们的官方网站。

应该带你到这个美妙的黑暗主题网站:D

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s--FxKvqXJz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/uploads/articles/wsrt633tm7dqj2878mvi.png)

现在继续并单击该按钮“开始您的项目”

它将向您显示此 auth0 页面,因此只需继续使用 GitHub 即可让您立即注册!

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s--sYbUNvsg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/uploads/articles/m917xywuq7m45lri4vv5.png)

然后只需使用您的 GitHub 凭据登录即可。

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s--fTTVNWTC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/uploads/articles/iqwv19ap6xfdzky7vo2v.png)

完成创建第一个帐户后,您可能已经在仪表板中,其中将列出您在 Supbase 中创建的所有项目。

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s--0Sysg1mg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/uploads/articles/2wa1awioz8bot83cw6g7.png)

现在单击“新建项目”并根据需要选择任何组织。我将选择我修改过的“个人”。

转到此页面时,只需填写以下字段:

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s--Ov_1xZW3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/uploads/articles/q5l4r6pcnznit2hsu629.png)

名称:“杂货应用程序”

数据库密码:“s0m3Str0ng_PassWord!@#”(您应该使用自己的密码)

地区:(选择您附近的任何地方)

完成后点击“创建新项目”!

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s--PeMu6JOJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/uploads/articles/yfkmmplom3ot92utnf16.png)

然后它会将您重定向到此页面。

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s--ers1q5ea--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/uploads/articles/9roq7vfbt7k4kewubjrf.png)

这将需要几分钟,所以请稍候:)

创建表

当 Supabase 设置好并且您创建了一个新项目时。它会带你进入这个页面。

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s--ts5QRlAH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/uploads/articles/h5h2msxc3hbylpw7nbyz.png)

现在让我们点击“创建一个新表”

我们将列出我们从数据库设计中获得的所有细节,所以这个设置应该很快。

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s--rR5VWS82--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/uploads/articles/1ucww2iareby5na636vp.png)

我建议取消选中“包括主键”,然后在创建表时添加一个主键。存在某种错误,我无法为uuid键设置默认值,以便在创建新记录时仅生成uuid

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s--EEi_TlwA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/uploads/articles/m8h06rakmolnkciu5jab.png)

然后只需点击右上角的“保存”即可最终创建表格。

创建该表后,我们可以继续添加主键uuid。单击该加号图标为表添加新列。

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s--OGMgMoNO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/uploads/articles/zxor6o90gzw1020uqyg9.png)

然后将该列命名为id,它将是一个主键和uuid类型,然后具有默认值“自动生成 UUID”,完成后单击“保存”。

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s--KAnthn0s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/uploads/articles/vbl4lcdy5sdaon5y58e9.png)

完成后,我们可以继续创建更多从数据库设计中定义的列。

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s--P6UOgyJ8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/uploads/articles/sqskongwnjc47rrfbknj.png)

接下来我们将为products创建一个表,并且我们将使用该表设置一个外键,因为产品属于用户。所以我们将学习如何快速做到这一点。

因此,假设您已经创建了主键id及其对应的列name作为 varchar,让我们创建最后一个字段created_by并将其设置为与app_users表链接的外键。

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s--LioXBEc1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/uploads/articles/xxc641voxkq1wzp8b94p.png)

现在点击底部的“添加外键关系”按钮

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s--tbC8KZeP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/uploads/articles/nkmv58z0g31yowj8na59.png)

然后选择表app_usersid字段,完成后单击“保存”

然后应该向您展示它现在与app_users表相关联,这非常令人惊奇。

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s--30n6T3D7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/uploads/articles/4z6nwx60x0m867kf4oof.png)

这就是设置外键所需要知道的全部内容。现在剩下的桌子由你决定。你有这个!

颤振数据模型

我们将使用freezed包和json_serializable设置我们的数据模型,并确保在您的项目中设置builder_runner

以下是我们的应用程序数据模型

import 'package:freezed_annotation/freezed_annotation.dart';

part 'application_models.freezed.dart';
part 'application_models.g.dart';

@freezed
class AppUser with _$AppUser {
  const factory AppUser({
    required String id,
    required String name,
    required String email,
  }) = _AppUser;

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

@freezed
class Grocery with _$Grocery {
  const Grocery._();
  const factory Grocery({
    required String id,
    required String name,
    @JsonKey(name: 'created_by')
        required String createdBy,
    @Default([])
    @JsonKey(
      name: 'grocery_products',
      fromJson: Grocery._productsFromJson,
      toJson: Grocery._productsToJson,
    )
        List<GroceryProduct>? groceryProducts,
  }) = _Grocery;

  bool get hasGroceryProducts => groceryProducts!.length > 0;

  List<Product?>? get products {
    if (!hasGroceryProducts) return [];

    return groceryProducts!.map((e) => e.product).toList();
  }

  factory Grocery.fromJson(Map<String, dynamic> json) =>
      _$GroceryFromJson(json);

  static List<GroceryProduct>? _productsFromJson(List<dynamic>? list) {
    if (list == null) {
      return [];
    }

    return list.map((e) => GroceryProduct.fromJson(e)).toList();
  }

  static List<Map<String, dynamic>>? _productsToJson(
      List<GroceryProduct>? list) {
    if (list == null) {
      return [];
    }

    return list.map((e) => e.toJson()).toList();
  }
}

@freezed
class GroceryDto with _$GroceryDto {
  const factory GroceryDto({
    required String name,
    @JsonKey(name: 'created_by') required String createdBy,
  }) = _GroceryDto;

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

@freezed
class Product with _$Product {
  const factory Product({
    required String id,
    required String name,
    @JsonKey(name: 'created_by') required String createdBy,
  }) = _Product;

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

@freezed
class ProductDto with _$ProductDto {
  const factory ProductDto({
    required String name,
    @JsonKey(name: 'created_by') required String createdBy,
  }) = _ProductDto;

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

@freezed
class GroceryProduct with _$GroceryProduct {
  const factory GroceryProduct({
    required String id,
    @JsonKey(name: 'grocery_id') required String groceryId,
    @JsonKey(name: 'product_id') required String productId,
    required int quantity,
    @JsonKey(name: 'products') Product? product,
    @Default('') String? unit,
  }) = _GroceryProduct;

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

@freezed
class GroceryProductDto with _$GroceryProductDto {
  const factory GroceryProductDto({
    @JsonKey(name: 'grocery_id') required String groceryId,
    @JsonKey(name: 'product_id') required String productId,
    @Default(1) int quantity,
    String? unit,
  }) = _GroceryProductDto;

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

@freezed
class AuthDto with _$AuthDto {
  const factory AuthDto({
    required String email,
    required String password,
    String? name,
  }) = _AuthDto;

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

进入全屏模式 退出全屏模式

上面的代码将为我们生成以下文件

  • 应用程序_models.freezed.dart

  • 应用程序_models.g.dart

我们不必编写所有内容,只需让它使用build_runner自动生成

为了为您分解我们的数据模型,我们看到我们有我们的杂货应用程序的主表

  • 应用用户

  • 杂货店

  • 产品

  • 杂货产品

DTO

  • 杂货店Dto

  • ProductDto

  • GroceryProductDto

  • AuthDto

但是那些带有“Dto”名称的数据模型是什么?

DTO 仅表示数据传输对象,我喜欢在我提出的任何 API 请求中使用 DTO。

数据传输对象是用于封装数据并将其从应用程序的一个子系统发送到另一个子系统的对象。 DTO 最常被 N 层应用程序中的服务层用于在其自身和 UI 层之间传输数据。

颤振设置

安装 Flutter 应用程序并进行设置。然后具有以下依赖项来使用它设置 Supbase。

包:

  • 斯帕基

  • postgrest

我添加了postgrest,因为我想从包中获取所有类型,而 Supabase 正在使用这些类型。

完成后,您可以继续设置您的 Supbase 客户端

import 'package:supabase/supabase.dart';

// use your own SUPABASE_URL
const String SUPABASE_URL = 'https://borayzhhitkyveigfijz.supabase.co';

// use your own SUPABASE_SECRET key
const String SUPABASE_SECRET =
    'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxOTMwODI5MCwiZXhwIjoxOTM0ODg0MjkwfQ.Kk1ckyjzCB98aWyBPtJsoWuTsbq2wyYfiUxG7fH4yAg';

final SupabaseClient supabase = SupabaseClient(SUPABASE_URL, SUPABASE_SECRET);

进入全屏模式 退出全屏模式

这些可以从 API 选项卡中的项目设置中找到。获取 SUPABASE_URL

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s--k3kHAgSH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/uploads/articles/5nskj0ggfwmb6l56n5c7.png)

和 SUPABASE_SECRET

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s--0yZi7VYM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/uploads/articles/tzytkzgiwiqcviwk80yd.png)

然后我们可以在已经设置好的时候进行查询!

Supabase 查询

如果你了解或熟悉 SQL,感觉应该很相似。

但是这些都是从 Supabase 本身自动生成的,所以不要担心,如果你不知道如何构造一个 Supabase 查询。只需检查每当您更新表或更改任何列时将为您动态生成的项目 API。

为了比较它,这是一个 RAW SQL 查询。

SELECT * FROM products

进入全屏模式 退出全屏模式

这就是你在 Dart 中使用 Supabase 编写查询的方式

supabase.from("products").select().execute();

进入全屏模式 退出全屏模式

确保最后一部分始终有execute,否则它将无法从products表中获取所有数据。

查询单个记录呢?

在 SQL 中,我们有,

SELECT * FROM products WHERE id = "uuid-string";

进入全屏模式 退出全屏模式

在 Supabase Dart 中,我们有,

supabase.from("products").select().eq("id", "uuid-string").single().execute();

进入全屏模式 退出全屏模式

您的 Supabase 项目还有更多查询要显示,因此请务必在此处查看

[图像](https://res.cloudinary.com/practicaldev/image/fetch/s--T6XfTl_l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/uploads/articles/mv7e4x6k7xs1pd65nf7t.png)

认证

在每个应用程序中,您可以保护用户数据的一件事就是拥有一个身份验证系统。因此,使用 Supabase 可以很容易地立即开始身份验证,因为它们提供了一个非常简单和直观的 API!

class AuthenticationService {
  final _logger = Logger();
  final _localStorageService = locator<LocalStorageService>();

  AppUser? _user = null;
  AppUser? get user => _user;
  bool get hasUser => _user != null;

  Future<void> initialize() async {}

  Future<AppUser?> signIn({required AuthDto payload}) async {}

  Future<AppUser?> signUp({required AuthDto payload}) async {}

  Future<void> signOut() async {}

  Future<AppUser?> fetchUser({required String id}) async {}

  Future<PostgrestResponse> _createUser(User user, AuthDto payload) {}
}

进入全屏模式 退出全屏模式

对上面的代码,进行分解。这取决于本地存储服务(共享首选项),我们将在其中存储 JWT 身份验证令牌/刷新令牌和可用于调试的 Logger。所以我喜欢随身携带一个 Logger。

我们有一个私有属性_user,我们用它自己的 getter 和一个布尔 getter 来存储我们的用户,以检查如果_user属性不为空,用户是否已登录。

initialize()方法内部是我们将执行自动登录的地方。因此,如果用户在其本地存储中存储了刷新令牌,我们将继续登录该用户并获取用户数据并将其存储在_user属性中,因此hasUser布尔 getter 将为真。

Future<void> initialize() async {
    final accessToken = await _localStorageService.getItem('token');
    _logger.i(accessToken);

    if (accessToken == null) {
      return;
    }

    final response = await supabase.auth.api.getUser(accessToken);

    if (response.error != null) {
      return;
    }

    final user = response.data!;
    _logger.i(user.toJson());
    await fetchUser(id: user.id);
  }

进入全屏模式 退出全屏模式

接下来是signIn方法,它的参数为AuthDto,其中包含emailpassword字段。当用户提供正确且现有的电子邮件时,我们将获取他们的访问令牌并将其存储在本地存储中。

Future<AppUser?> signIn({required AuthDto payload}) async {
    final response = await supabase.auth.signIn(
      email: payload.email,
      password: payload.password,
    );

    if (response.error != null) {
      _logger.e(response.error!.message);
      return null;
    }
    _logger.i(response.data);
    await _localStorageService.setItem('token', response.data!.accessToken);
    return await fetchUser(id: response.data!.user!.id);
  }

进入全屏模式 退出全屏模式

每当我们有新用户想要使用我们的应用程序时,我们都会使用signUp方法。创建新用户时,我们获取访问令牌并将其保存到本地存储。我们还将继续在app_users表中创建一个新的用户记录,但它将使用另一种称为_createUser的方法

Future<AppUser?> signUp({required AuthDto payload}) async {
    final response =
        await supabase.auth.signUp(payload.email, payload.password);

    if (response.error != null) {
      _logger.e(response.error!.message);
      return null;
    }

    final user = response.data!.user!;
    _logger.i(user.toJson());
    await _createUser(user, payload);
    await _localStorageService.setItem('token', response.data!.accessToken);
    return await fetchUser(id: user.id);
  }

进入全屏模式 退出全屏模式

_createdUser将在app_users表中创建一个新的用户记录。

Future<PostgrestResponse> _createUser(User user, AuthDto payload) {
    return supabase
        .from("app_users")
        .insert(
          AppUser(
            id: user.id,
            name: payload.name!,
            email: user.email,
          ),
        )
        .execute();
  }

进入全屏模式 退出全屏模式

然后是signOut这已经是不言自明的了。这里我们只是在用户决定为signOut时从本地存储中删除访问令牌

Future<void> signOut() async {
    final response = await supabase.auth.signOut();

    if (response.error != null) {
      _logger.e(response.error!.message);
      return;
    }
    _logger.i(response.rawData);
    await _localStorageService.removeItem('token');
    return;
  }

进入全屏模式 退出全屏模式

最后,我们有fetchUser方法,它将获取当前经过身份验证的用户记录,因此我们将在需要时在整个应用程序中获取他们的信息。

Future<AppUser?> fetchUser({required String id}) async {
    final response = await supabase
        .from("app_users")
        .select()
        .eq('id', id)
        .single()
        .execute();

    _logger.i(
      'Count: ${response.count}, Status: ${response.status}, Data: ${response.data}',
    );

    if (response.error != null) {
      _logger.e(response.error!.message);
      return null;
    }

    _logger.i(response.data);
    final data = AppUser.fromJson(response.data);
    _user = data;

    return data;
  }

进入全屏模式 退出全屏模式

Supabase 服务

我们完成了数据模型和身份验证的处理,然后我们可以为我们的应用程序创建和处理读写操作。由于抽象的概念,我们不必为相同的功能编写大量代码,我们将编写更少的代码并将此功能扩展到需要它的其他服务。

以下将是处理 CRUD 操作(Cread、Read、Update、Delete)的抽象类

import 'package:logger/logger.dart';
import 'package:postgrest/postgrest.dart';
import 'package:supagrocery/app/app.locator.dart';
import 'package:supagrocery/app/supabase_api.dart';
import 'package:supagrocery/services/authentication_service.dart';

abstract class SupabaseService<T> {
  final _authService = locator<AuthenticationService>();
  final _logger = Logger();

  String tableName() {
    return "";
  }

  Future<PostgrestResponse> all() async {
    _logger.i(tableName());
    final response = await supabase
        .from(tableName())
        .select()
        .eq('created_by', _authService.user!.id)
        .execute();
    _logger.i(response.toJson());
    return response;
  }

  Future<PostgrestResponse> find(String id) async {
    _logger.i(tableName() + ' ' + id);
    final response = await supabase
        .from(tableName())
        .select()
        .eq('id', id)
        .single()
        .execute();
    _logger.i(response.toJson());
    return response;
  }

  Future<PostgrestResponse> create(Map<String, dynamic> json) async {
    _logger.i(tableName() + ' ' + json.toString());
    final response = await supabase.from(tableName()).insert(json).execute();
    _logger.i(response.toJson());
    return response;
  }

  Future<PostgrestResponse> update({
    required String id,
    required Map<String, dynamic> json,
  }) async {
    _logger.i(tableName() + ' ' + json.toString());
    final response =
        await supabase.from(tableName()).update(json).eq('id', id).execute();
    _logger.i(response.toJson());
    return response;
  }

  Future<PostgrestResponse> delete(String id) async {
    _logger.i(tableName() + ' ' + id);
    final response =
        await supabase.from(tableName()).delete().eq('id', id).execute();
    _logger.i(response.toJson());
    return response;
  }
}

进入全屏模式 退出全屏模式

这个抽象类依赖于我们刚刚创建的AuthenticationService,因此我们可以在每次用户在我们的数据库中创建记录时附加用户 ID。

我们将为每个需要它的要素服务覆盖tableName。因此,在创建ProductServiceGroceryService时,我们只需扩展这个类,并用对应的表名覆盖tableName

这是ProductService的示例

import 'package:postgrest/postgrest.dart';
import 'package:supagrocery/app/app.locator.dart';
import 'package:supagrocery/app/supabase_api.dart';
import 'package:supagrocery/datamodels/application_models.dart';
import 'package:supagrocery/services/authentication_service.dart';
import 'package:supagrocery/services/supabase_service.dart';

class ProductService extends SupabaseService<Product> {
  final _authService = locator<AuthenticationService>();

  @override
  String tableName() {
    return "products";
  }

  Future<PostgrestResponse> fetchProducts() async {
    return await supabase
        .from("products")
        .select("*")
        .eq('created_by', _authService.user!.id)
        .execute();
  }
}

进入全屏模式 退出全屏模式

这也将具有我们创建的SupabaseService抽象类中的方法,并且不必重写任何内容,我们只需要覆盖tableName并返回该表的名称。有了ProductService内部的内容,我们就可以编写任何与业务逻辑相关的方法。

那么这是我们的GroceryService

import 'package:postgrest/postgrest.dart';
import 'package:supagrocery/app/app.locator.dart';
import 'package:supagrocery/app/supabase_api.dart';
import 'package:supagrocery/datamodels/application_models.dart';
import 'package:supagrocery/services/supabase_service.dart';

import 'authentication_service.dart';

class GroceryService extends SupabaseService<Grocery> {
  final _authService = locator<AuthenticationService>();

  @override
  String tableName() {
    return "groceries";
  }

  Future<PostgrestResponse> fetchGroceryList({required String id}) async {
    return await supabase
        .from("groceries")
        .select("*, grocery_products(*, products(*) )")
        .eq('id', id)
        .eq('created_by', _authService.user!.id)
        .single()
        .execute();
  }

  Future<PostgrestResponse> addProductsToList({
    required String id,
    required List<Product?> products,
  }) async {
    return await supabase
        .from("grocery_products")
        .insert(
          products.map((e) {
            return GroceryProductDto(
              groceryId: id,
              productId: e!.id,
            ).toJson();
          }).toList(),
        )
        .execute();
  }

  Future<PostgrestResponse> markProductChecked(
      {required GroceryProduct payload}) async {
    return await supabase
        .from("grocery_products")
        .update(payload.toJson())
        .eq('id', payload.id)
        .execute();
  }

  Future<PostgrestResponse> removeProduct({required String id}) async {
    return await supabase
        .from("grocery_products")
        .delete()
        .eq('id', id)
        .execute();
  }
}

进入全屏模式 退出全屏模式

总结

我们涵盖了数据库设计、设置 Supabase、使用 Supabase API 实现身份验证系统以及使用抽象轻松实现新功能。

我希望这给了你一个想法,并且在任何方面都有用。

感谢您的阅读,希望您喜欢!

链接到存储库

Logo

PostgreSQL社区为您提供最前沿的新闻资讯和知识内容

更多推荐