使用 Firebase Data Connect 构建应用(iOS / Swift)

1. 概览

此 Codelab 将引导您完成将 Firebase Data Connect 与 Cloud SQL 数据库集成的过程,以便使用 SwiftUI 构建适用于 iOS 的电影评价应用

您将学习如何使用 Firebase Data Connect 将 iOS 应用连接到 Cloud SQL 数据库,从而实现电影评价数据的流畅同步。

在本 Codelab 结束时,您将拥有一个功能齐全的 iOS 应用,用户可以通过该应用浏览电影并将电影标记为收藏,所有这些操作都由 Firebase Data Connect 强大的功能支持的 Cloud SQL 数据库提供后盾。

学习内容

此 Codelab 将教您如何:

  • 使用 Firebase Emulator Suite 设置 Firebase Data Connect,以缩短交付周期。
  • 使用 Data Connect 和 GraphQL 设计数据库架构
  • 根据数据库架构创建类型安全的 Swift SDK,并将其添加到 Swift 应用中。
  • 实现用户身份验证并将其与 Firebase Data Connect 集成,以确保用户数据的安全。
  • 使用由 GraphQL 提供支持的查询和更改,在 Cloud SQL 中检索、更新、删除和管理数据。
  • (可选)将 Data Connect 服务部署到生产环境。

前提条件

  • 最新版本的 Xcode
  • Codelab 示例代码。您将在 Codelab 的第一个步骤中下载示例代码。

2. 设置示例项目

创建 Firebase 项目

  1. 使用您的 Google 账号登录 Firebase 控制台
  2. Firebase 控制台中,点击创建 Firebase 项目
  3. 输入 Firebase 项目的名称(例如“Friendly Flix”),然后点击继续
  4. 系统可能会要求您为 Firebase 项目启用 AI 辅助功能。在此 Codelab 中,您的选择无关紧要。
  5. 系统可能会要求您启用 Google Analytics。在此 Codelab 中,您的选择没有影响。
  6. 大约一分钟后,您的 Firebase 项目就准备就绪了。点击继续

下载代码

运行以下命令以克隆此 Codelab 的示例代码。这将在您的机器上创建一个名为 codelab-dataconnect-ios 的目录:

git clone https://github.com/FirebaseExtended/codelab-dataconnect-ios`

如果您的机器上没有 git,您也可以直接从 GitHub 下载代码。

添加 Firebase 配置

Firebase SDK 使用配置文件连接到您的 Firebase 项目。在 Apple 平台上,此文件名为 GoogleServices-Info.plist。在此步骤中,您将下载配置文件并将其添加到 Xcode 项目中。

  1. Firebase 控制台中,选择左侧导航栏中的项目概览
  2. 点击 iOS+ 按钮以选择平台。当系统提示您输入 Apple 软件包 ID 时,请使用 com.google.firebase.samples.FriendlyFlix
  3. 点击注册应用,然后按照说明下载 GoogleServices-Info.plist 文件。
  4. 将下载的文件移至您刚刚下载的代码的 start/FriendlyFlix/app/FriendlyFlix/FriendlyFlix/ 目录,替换现有的 GoogleServices-Info.plist 文件。
  5. 然后,点击几次下一步,在 Firebase 控制台中完成设置项目(您无需将 SDK 添加到应用,因为此操作已在起始项目中为您完成)。
  6. 最后,点击继续前往控制台以完成设置流程。

3. 设置 Data Connect

安装

自动安装

codelab-dataconnect-ios/FriendlyFlix 目录中运行以下命令:

curl -sL https://firebase.tools/dataconnect | bash

此脚本会尝试为您设置开发环境并启动基于浏览器的 IDE。此 IDE 提供了一些工具(包括预捆绑的 VS Code 扩展程序),可帮助您管理架构,定义要在应用中使用的查询和更改,以及生成强类型 SDK。

运行脚本后,VS Code 应会自动打开。

完成此操作后,您就可以在本地目录中运行 VS Code 来启动 VS Code:

code .

手动安装

  1. 安装 Visual Studio Code
  2. 安装 Node.js
  3. 在 VS Code 中,打开 codelab-dataconnect-ios/FriendlyFlix 目录。
  4. Visual Studio Code Marketplace 安装 Firebase Data Connect 扩展程序。

在项目中初始化 Data Connect

在左侧面板中,点击 Firebase 图标以打开 Data Connect VS Code 扩展程序界面

  1. 点击使用 Google 账号登录按钮。系统随即会打开一个浏览器窗口;请按照说明使用您的 Google 账号登录该扩展程序。
  2. 点击关联 Firebase 项目按钮,然后在控制台中选择您之前创建的项目。
  3. 点击 Run firebase init 按钮,然后按照集成终端中的步骤操作。

配置 SDK 生成

点击 Run firebase init 按钮后,Firebase Data Connect 扩展程序应会为您初始化 dataconnect 目录。

在 VS Code 中,打开 dataconnect/connector/connector.yaml 文件,您会看到默认配置。

请更新配置并使用以下设置,确保生成的代码可与此 Codelab 搭配使用。具体而言,请确保将 connectorId 设置为 friendly-flix,并将 Swift 软件包设置为 FriendlyFlixSDK

connectorId: "friendly-flix"
generate:
  swiftSdk:
    outputDir: "../../app"
    package: "FriendlyFlixSDK"
    observablePublisher: observableMacro

这些设置的含义如下:

  • connectorId - 此连接器的唯一名称。
  • outputDir - 生成的 Data Connect SDK 的存储路径。此路径相对于包含 connector.yaml 文件的目录。
  • package - 要为生成的 Swift 软件包使用的软件包名称。

保存此文件后,Firebase Data Connect 会为您生成一个名为 FriendlyFlixSDK 的 Swift 软件包,并将其放置在 FriendlyFlix 项目文件夹旁边。

启动 Firebase 模拟器

在 VS Code 中,切换到 Firebase 视图,然后点击 Start emulators 按钮。

这将在集成的终端中启动 Firebase 模拟器。输出应如下所示:

npx -y firebase-tools@latest emulators:start --project <your-project-id>

将生成的软件包添加到您的 Swift 应用

  1. 在 Xcode 中打开 FriendlyFlix/app/FriendlyFlix/FriendlyFlix.xcodeproj
  2. 依次选择 File > Add Package Dependencies...
  3. 点击 Add Local...(添加本地...),然后从 FriendlyFlix/app 文件夹中添加 FriendlyFlixSDK 软件包
  4. 等待 Xcode 解析软件包依赖项。
  5. Choose Package Products for FriendlyFlixSDK 对话框中,选择 FriendlyFlix 作为目标,然后点击 Add Package

将 iOS 应用配置为使用本地模拟器

  1. 打开 FriendlyFlixApp.swift。 (您可以按 CMD + Shift + O 打开快速打开对话框,然后输入“FriendlyFlixApp”以快速找到该文件)
  2. 为您的架构导入 Firebase、Firebase Auth、Firebase Data Connect 以及生成的 SDK
  3. 在初始化程序中,配置 Firebase。
  4. 确保 DataConnect 和 Firebase Auth 使用本地模拟器。
import SwiftUI
import os
import Firebase
import FirebaseAuth
import FriendlyFlixSDK
import FirebaseDataConnect

@main
struct FriendlyFlixApp: App {
  ...

  init() {
    FirebaseApp.configure()
    if useEmulator {
      DataConnect.friendlyFlixConnector.useEmulator(port: 9399)
      Auth.auth().useEmulator(withHost: "localhost", port: 9099)
    }

    authenticationService = AuthenticationService()
  }

  ...

}
  1. 目标下拉菜单中选择一个 iOS 模拟器。
  2. 在 Xcode 中按 CMD+R(或点击 Run 按钮),以在模拟器上运行应用。

4. 定义架构并预先填充数据库

在本部分中,您将在架构中定义电影应用中关键实体的结构和这些实体之间的关系。MovieMovieMetaData 等实体会映射到数据库表,并使用 Firebase Data Connect 和 GraphQL 架构指令建立关系。

核心实体和关系

此电影追踪器应用的数据模型由多个实体组成,您将在本 Codelab 的学习过程中创建这些实体。您将先创建核心实体,然后随着实现越来越多的功能,添加这些功能所需的实体。

在此步骤中,您将创建 MovieMovieMetadata 类型。

电影

Movie 类型定义了电影实体的主要结构,包括 titlegenrereleaseYearrating 等字段。

在 VS Code 中,将 Movie 类型定义添加到 dataconnect/schema/schema.gql

type Movie @table {
  id: UUID! @default(expr: "uuidV4()")
  title: String!
  imageUrl: String!
  releaseYear: Int
  genre: String
  rating: Float
  description: String
  tags: [String]
}

MovieMetadata

MovieMetadata 类型会与 Movie 类型建立一对一关系。其中包含电影导演等额外数据。

MovieMetadata 表定义添加到 dataconnect/schema/schema.gql 文件中:

type MovieMetadata @table {
  movie: Movie! @ref
  director: String
}

自动生成的字段和默认值

该架构使用 @default(expr: "uuidV4()") 等表达式自动生成唯一 ID 和时间戳。例如,在创建新记录时,系统会自动在 Movie 类型的 id 字段中填充 UUID。

为电影和电影元数据插入模拟数据

架构定义完毕后,您现在可以使用模拟数据预先填充数据库以进行测试。

  1. 在 Finder 中,将 finish/FriendlyFlix/dataconnect/moviedata_insert.gql 复制到 start/FriendlyFlix/dataconnect 文件夹。
  2. 在 VS Code 中,打开 dataconnect/moviedata_insert.gql
  3. 确保 Firebase Data Connect 扩展程序中的模拟器正在运行。
  4. 您应该会在文件顶部看到 Run (local) 按钮。点击此按钮可将模拟电影数据插入数据库。
  5. 查看 Data Connect Execution 终端,确认数据已成功添加。

数据准备就绪后,请继续执行下一步,了解如何在 Data Connect 中创建查询。

5. 检索和显示电影

在本部分中,您将实现用于显示电影列表的功能。

首先,您将学习如何创建一个查询,以从 movies 表中检索所有电影。Firebase Data Connect 会为类型安全的 SDK 生成代码,您可以使用这些代码来执行查询,并在应用的界面中显示检索到的电影。

定义 ListMovies 查询

Firebase Data Connect 中的查询是使用 GraphQL 编写的,可让您指定要提取哪些字段。在 FriendlyFlix 中,用于显示电影的屏幕需要包含以下字段:titledescriptionreleaseYearratingimageUrl。此外,由于这是一款 SwiftUI 应用,因此您需要 id 来帮助处理 SwiftUI 视图身份。

在 VS Code 中,打开 dataconnect/connector/queries.gql 文件并添加 ListMovies 查询:

query ListMovies @auth(level: PUBLIC) {
  movies {
    id
    title
    imageUrl
    releaseYear
    genre
    rating
    tags
    description
  }
}

如需测试新查询,请点击 Run (local) 按钮,对本地数据库执行查询。数据库中的电影列表应显示在 Data Connect 执行终端的结果部分下。

将 ListMovies 查询连接到应用的主屏幕

现在,您已在 Data Connect 模拟器中测试了查询,接下来可以从应用内调用该查询了。

保存 queries.gql 后,Firebase Data Connect 会在 FriendlyFlixSDK 软件包中生成与 ListMovies 查询对应的代码。

在 Xcode 中,打开 Movie+DataConnect.swift,然后添加以下代码以将 ListMoviesQuery.Data.Movie 映射到 Movie

import FirebaseDataConnect
import FriendlyFlixSDK

extension Movie {
  init(from: ListMoviesQuery.Data.Movie) {
    id = from.id
    title = from.title
    description = from.description ?? ""
    releaseYear = from.releaseYear
    rating = from.rating
    imageUrl = from.imageUrl
  }
}

打开 HomeScreen.swift 文件,然后使用以下代码段对其进行更新。

import SwiftUI
import FirebaseDataConnect
import FriendlyFlixSDK

struct HomeScreen: View {
  ...

  private var connector = DataConnect.friendlyFlixConnector
  let heroMoviesRef: QueryRefObservation<ListMoviesQuery.Data, ListMoviesQuery.Variables>

  init() {
    heroMoviesRef = connector.listMoviesQuery.ref()
  }
}

extension HomeScreen {
  ...

  private var heroMovies: [Movie] {
    heroMoviesRef.data?.movies.map(Movie.init) ?? []
  }

 private var topMovies: [Movie] {
    heroMoviesRef.data?.movies.map(Movie.init) ?? []
  }

  private var watchList: [Movie] {
    heroMoviesRef.data?.movies.map(Movie.init) ?? []
  }

  ...
}

listMoviesQuery() 查询是在您保存 queries.gql 时由 Data Connect 生成的。如需查看其 Swift 实现,请查看 FriendlyFlixSDK 软件包中的 FriendlyFlixOperations.swift 文件。

运行应用

在 Xcode 中,点击 Run 按钮以在 iOS 模拟器中启动应用。

应用启动后,您应该会看到如下所示的屏幕:

您可能会注意到,应用的所有区域(主推版块、热门电影和观看列表)都显示相同的列表。这是因为您对所有这些视图使用了相同的查询。在接下来的部分中,您将实现自定义查询。

6. 显示主打电影和热门电影

在此步骤中,您将重点更新主推部分(即首页顶部的醒目轮播界面)和下方的热门电影部分中电影的显示方式。

目前,ListMovies 查询会检索所有电影。为了优化这些版块的显示效果,您需要限制每个查询返回的电影数量。ListMovies 查询的当前实现尚不支持对结果进行限制,您将在此部分中添加对限制和排序的支持。

增强了 ListMovies 查询

打开 queries.gql 并按如下方式更新 ListMovies,以添加对排序和限制的支持:

query ListMovies(
  $orderByRating: OrderDirection
  $orderByReleaseYear: OrderDirection
  $limit: Int
) @auth(level: PUBLIC) {
  movies(
    orderBy: [{ rating: $orderByRating }, { releaseYear: $orderByReleaseYear }]
    limit: $limit
  ) {
    id
    title
    description
    releaseYear
    rating
    imageUrl
  }
}

这样,您就可以限制查询返回的电影数量,并按评分和发行年份对结果集进行排序。

保存此文件后,Firebase Data Connect 会自动重新生成 FriendlyFlixSDK 中的代码。在下一步中,您可以更新 HomeScreen.swift 中的代码,以便使用这些额外的功能。

在界面中使用增强型查询

返回 Xcode,对 HomeScreen.swift 进行必要的更改。

首先,更新 heroMoviesRef 以提取最近 3 部上映的电影:

struct HomeScreen {
  ...

  init() {
    heroMoviesRef = connector.listMoviesQuery
      .ref { optionalVars in
        optionalVars.limit = 3
        optionalVars.orderByReleaseYear = .DESC
      }

  }
}

接下来,为热门电影设置另一个查询引用,并将过滤条件设置为评分最高的 5 部电影:

struct HomeScreen {
  ...

  let topMoviesRef: QueryRefObservation<ListMoviesQuery.Data, ListMoviesQuery.Variables>

  init() {
    heroMoviesRef = ...

    topMoviesRef = connector.listMoviesQuery
      .ref { optionalVars in
        optionalVars.limit = 5
        optionalVars.orderByRating = .DESC
      }
  }
}

最后,更新用于将此查询的结果与界面相关联的计算属性:

extension HomeScreen {
  ...

  private var topMovies: [Movie] {
    topMoviesRef.data?.movies.map(Movie.init) ?? []
  }

}

看看它的实际功效

再次运行该应用,即可在主推版块中查看最近 3 部电影,并在热门电影版块中查看评分最高的 5 部电影:

7. 显示电影和演员详情

用户现在可以浏览电影了。点按电影卡片后,系统会显示该电影的一些详细信息,但您可能已经注意到,这些详细信息缺少一些... 详细信息!

这是因为,我们只会提取与电影主推内容和热门电影部分相关的电影详细信息,即电影名、简短说明和图片网址。

在电影详情页面上,我们希望显示有关电影的更多信息。在本部分中,您将对应用进行增强,使其能够在详情页面上显示电影的演员和任何评价。

为此,您需要执行以下几项操作:

  • 增强架构以支持电影演员和评价
  • 编写 Firebase Data Connect 查询,以提取指定电影的详细信息
  • 在电影详情页面上显示结果

完善架构

在 VS Code 中,打开 dataconnect/schema/schema.gql,然后为 ActorMovieActor 添加架构定义。

## Actors
## An actor can participate in multiple movies; movies can have multiple actors
## Movie - Actors (or vice versa) is a many to many relationship
type Actor @table {
  id: UUID!
  imageUrl: String!
  name: String! @col(name: "name", dataType: "varchar(30)")
}

## Join table for many-to-many relationship for movies and actors
## The 'key' param signifies the primary key(s) of this table
## In this case, the keys are [movieId, actorId], the generated fields of the reference types [movie, actor]
type MovieActor @table(key: ["movie", "actor"]) {
  ## @ref creates a field in the current table (MovieActor) that holds the primary key of the referenced type
  ## In this case, @ref(fields: "id") is implied
  movie: Movie!
  ## movieId: UUID! <- this is created by the implied @ref, see: implicit.gql

  actor: Actor!
  ## actorId: UUID! <- this is created by the implied  @ref, see: implicit.gql

  role: String! ## "main" or "supporting"
}

为 actor 添加模拟数据

架构更新后,您现在可以向数据库中填充更多模拟数据以进行测试。

  1. 在 Finder 中,将 finish/FriendlyFlix/dataconnect/moviededetails_insert.gql 复制到 start/FriendlyFlix/dataconnect 文件夹。
  2. 在 VS Code 中,打开 dataconnect/moviededetails_insert.gql
  3. 确保 Firebase Data Connect 扩展程序中的模拟器正在运行。
  4. 您应该会在文件顶部看到 Run (local) 按钮。点击此按钮可将模拟电影数据插入数据库。
  5. 检查 Data Connect 执行终端,确认数据已成功添加。

数据就绪后,请继续执行下一步,定义用于提取电影详情的查询。

定义 GetMovieById 查询

在 VS Code 中,打开 dataconnect/connector/queries.gql 文件并添加 GetMovieById 查询:

## Get movie by id
query GetMovieById($id: UUID!) @auth(level: PUBLIC) {
  movie(id: $id) {
    id
    title
    imageUrl
    releaseYear
    genre
    rating
    description
    tags
    metadata: movieMetadatas_on_movie {
      director
    }
    mainActors: actors_via_MovieActor(where: { role: { eq: "main" } }) {
      id
      name
      imageUrl
    }
    supportingActors: actors_via_MovieActor(
      where: { role: { eq: "supporting" } }
    ) {
      id
      name
      imageUrl
    }
  }
}

将 GetMovieById 查询连接到 MovieDetailsView

在 Xcode 中,打开 MovieDetailsView.swift 文件,然后更新 movieDetails 计算属性,使其与以下代码相匹配:

import NukeUI
import SwiftUI
import FirebaseDataConnect
import FriendlyFlixSDK

@MainActor
struct MovieDetailsView: View {
  private var movie: Movie

  private var movieDetails: MovieDetails? {
    DataConnect.friendlyFlixConnector
      .getMovieByIdQuery
      .ref(id: movie.id)
      .data?.movie.map { movieDetails in
        MovieDetails(
          title: movieDetails.title,
          description: movieDetails.description ?? "",
          releaseYear: movieDetails.releaseYear,
          rating: movieDetails.rating ?? 0,
          imageUrl: movieDetails.imageUrl,
          mainActors: movieDetails.mainActors.map { mainActor in
            MovieActor(id: mainActor.id,
                       name: mainActor.name,
                       imageUrl: mainActor.imageUrl)
          },
          supportingActors: movieDetails.supportingActors.map{ supportingActor in
            MovieActor(id: supportingActor.id,
                       name: supportingActor.name,
                       imageUrl: supportingActor.imageUrl)
          },
          reviews: []
        )
      }
  }

  public init(movie: Movie) {
    self.movie = movie
  }
}

运行应用

在 Xcode 中,点击 Run 按钮,在 iOS 模拟器上启动应用。

应用启动后,点按电影卡片即可显示电影详情。它应如下所示:

8. 实现用户身份验证

目前,该应用会显示非个性化的电影和演员信息。在以下步骤中,您将实现将数据与已登录用户相关联的功能。首先,您需要允许用户将电影添加到其个人观看列表。

您需要先建立用户身份,然后才能实现观察名单功能。为此,您将集成 Firebase Authentication,以允许用户登录应用。

您可能已经在主屏幕的右上角看到了用户头像按钮。点按此按钮后,您会进入一个屏幕,用户可以在其中使用电子邮件地址和密码进行注册或登录。

用户成功登录后,您的应用需要存储其基本详细信息,主要是其唯一的用户 ID 和所选用户名。

启用 Firebase Authentication

在项目的 Firebase 控制台中,前往“Authentication”(身份验证)部分,然后启用 Firebase Authentication。然后,启用电子邮件地址/密码身份验证提供方。

在本地项目文件夹中,找到 firebase.json 并按如下所示进行更新,以启用 Firebase Authentication 模拟器。

{
  "emulators": {
    "dataconnect": {
    },
    "auth": {
    }
  },
  "dataconnect": {
    "source": "dataconnect"
  }
}

之后,您需要停止并重启 Firebase 模拟器,使更改生效。

实现身份验证处理程序

在下一部分中,您将实现将用户身份验证与数据库相关联的逻辑。这涉及创建一个监听成功登录的身份验证处理程序。

用户通过身份验证后,此处理脚本会自动触发在您的数据库中创建相应账号。

在 Xcode 中,打开 AuthenticationService.swift 文件并添加以下代码:

import Foundation
import Observation
import os
import FirebaseAuth

enum AuthenticationState {
  case unauthenticated
  case authenticating
  case authenticated
}

@Observable
class AuthenticationService {
  private let logger = Logger(subsystem: "FriendlyFlix", category: "auth")

  var presentingAuthenticationDialog = false
  var presentingAccountDialog = false

  var authenticationState: AuthenticationState = .unauthenticated
  var user: User?
  private var authenticationListener: AuthStateDidChangeListenerHandle?

  init() {
    authenticationListener = Auth.auth().addStateDidChangeListener { auth, user in
      if let user {
        self.authenticationState = .authenticated
        self.user = user
      } else {
        self.authenticationState = .unauthenticated
      }
    }
  }

  private var onSignUp: ((User) -> Void)?
  public func onSignUp(_ action: @escaping (User) -> Void) {
    onSignUp = action
  }

  func signInWithEmailPassword(email: String, password: String) async throws {
    try await Auth.auth().signIn(withEmail: email, password: password)
    authenticationState = .authenticated
  }

  func signUpWithEmailPassword(email: String, password: String) async throws {
    try await Auth.auth().createUser(withEmail: email, password: password)

    if let onSignUp, let user = Auth.auth().currentUser {
      logger
        .debug(
          "User signed in \(user.displayName ?? "(no fullname)") with email \(user.email ?? "(no email)")"
        )
      onSignUp(user)
    }

    authenticationState = .authenticated
  }

  func signOut() throws {
    try Auth.auth().signOut()
    authenticationState = .unauthenticated
  }
}

这是一个通用身份验证处理脚本,可让您使用 onSignUp 注册一个在用户登录后调用的闭包。

然后,您可以在该闭包内在数据库中创建新用户账号。不过,在此之前,您需要创建一个 Mutation,以便在数据库中创建或更新新用户。

向架构中添加 User 实体

User 类型用于定义用户实体。用户可以通过发表评价或将电影添加为收藏来与电影互动。

在 VS Code 中,打开 dataconnect/schema/schema.gql 文件并添加以下 User 表定义:

## Users
## A user can leave reviews for movies
## user-reviews is a one to many relationship, movie-reviews is a one to many relationship, movie:user is a many to many relationship
type User @table {
  id: String! @col(name: "user_auth")
  username: String! @col(name: "username", dataType: "varchar(50)")
}

定义用于插入或更新用户的更改

在 VS Code 中,打开 dataconnect/connector/mutations.gql 文件并添加 UpsertUser 更改:

mutation UpsertUser($username: String!) @auth(level: USER) {
  user_upsert(
    data: {
      id_expr: "auth.uid"
      username: $username
    }
  )
}

成功登录后创建新用户

在 Xcode 中,打开 FriendlyFlixApp.swift,然后将以下代码添加到初始化程序:

@main
struct FriendlyFlixApp: App {

  ...

  init() {
    ...
    authenticationService = AuthenticationService()
    authenticationService?.onSignUp { user in
      let userName = String(user.email?.split(separator: "@").first ?? "(unknown)")
      Task {
        try await DataConnect.friendlyFlixConnector
          .upsertUserMutation.execute(username: userName)
      }
    }
  }

  var body: some Scene {
    ...
  }
}

每当用户使用 Firebase Authentication 成功注册时,此代码都会使用为您生成的 upsertUserMutation Firebase Data Connect 插入新用户(或更新具有相同 ID 的现有用户)。

看看它的实际功效

如需验证此设置是否有效,请先在 iOS 应用中注册:

  • 如果您尚未完成,请停止并重启 Firebase 模拟器,确保 Firebase Authentication 模拟器正在运行。
  • 在 Xcode 中,点击 Run 按钮,在 iOS 模拟器上启动应用。
  • 点击屏幕右上角的头像图标。
  • 切换到注册流程,然后注册应用。

然后,查询数据库以验证应用是否为用户创建了新用户账号:

  • 在 VS Code 中,打开 dataconnect/schema/schema.gql,然后点击 User 实体上的读取数据
  • 这将创建一个名为 User_read.gql 的新查询文件
  • 点击在本地运行,查看“用户”表格中的所有用户
  • 现在,在“Data Connect 执行”窗格中,您应该会看到刚刚使用 注册的用户的账号

9. 管理喜爱的电影

在此 Codelab 的此部分中,您将在电影评价应用中实现用户互动,具体而言,让用户管理他们喜爱的电影。标记为收藏的电影会显示在应用的“观看列表”部分。

增强架构以支持收藏夹

FavoriteMovie 类型是一个联接表,用于处理用户与其喜爱的电影之间的多对多关系。每个表都将 UserMovie 相关联。

将代码段复制并粘贴到 dataconnect/schema/schema.gql 文件中:

type FavoriteMovie
  @table(name: "FavoriteMovies", singular: "favorite_movie", plural: "favorite_movies", key: ["user", "movie"]) {
  ## @ref is implicit
  user: User!
  movie: Movie!
}

定义用于添加和移除收藏夹的更改

在应用显示用户喜爱的电影之前,用户需要指明哪些电影是他们的最爱。为此,您首先需要添加两个更改,分别将电影标记为用户的收藏之一,或将其从收藏中移除。

  1. 在 VS Code 中,打开 dataconnect/connector/mutations.gql 中的 mutations.gql
  2. 添加以下更改,以处理将电影添加为收藏的操作:
## Add a movie to the user's favorites list
mutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
}

## Remove a movie from the user's favorites list
mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}

将更改关联到应用的界面

用户可以点击电影详情页面上的红心图标,将电影标记为收藏。

如需将您刚刚创建的更改关联到应用的界面,请在 MovieCardView 中进行以下更改:

  1. 导入 FriendlyFlixSDK 并设置连接器
import NukeUI
import os
import SwiftUI
import FirebaseDataConnect
import FriendlyFlixSDK

struct MovieCardView: View {
  private let logger = Logger(subsystem: "FriendlyFlix", category: "moviecard")
  @Environment(\.dismiss) private var dismiss
  private var connector = DataConnect.friendlyFlixConnector

  ...
}
  1. 实现 toggleFavourite 方法。每当用户点按 MovieCardView 中的红心图标时,系统都会调用此方法:
struct MovieCardView {

  ...

  private func toggleFavourite() {
    Task {
      if isFavourite {
        let _ = try await connector.deleteFavoritedMovieMutation.execute(movieId: movie.id)
      } else {
        let _ = try await connector.addFavoritedMovieMutation.execute(movieId: movie.id)
      }
    }
  }
}

这会更新数据库中当前电影的收藏状态。最后一个步骤是确保界面状态相应地反映出来。

定义一个查询,用于确定某部电影是否已标记为“收藏”

  1. 在 VS Code 中,打开 dataconnect/connector 中的 queries.gql
  2. 添加以下查询,以检查电影是否已标记为收藏:
query GetIfFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie(key: { userId_expr: "auth.uid", movieId: $movieId }) {
    movieId
  }
}
  1. 在 Xcode 中,实例化对 GetIfFavoritedMovie 查询的引用,并实现计算属性,用于确定此 MovieCardView 上显示的电影是否被标记为当前用户的收藏。
struct MovieCardView: View {

  ...

  public init(showDetails: Bool, movie: Movie) {
    self.showDetails = showDetails
    self.movie = movie

    isFavouriteRef = connector.getIfFavoritedMovieQuery.ref(movieId: movie.id)
  }

  // MARK: - Favourite handling

  private let isFavouriteRef: QueryRefObservation<
    GetIfFavoritedMovieQuery.Data,
    GetIfFavoritedMovieQuery.Variables
  >
  private var isFavourite: Bool {
    isFavouriteRef.data?.favorite_movie?.movieId != nil
  }

  ...

}
  1. 更新 toggleFavourite 中的代码,以便在用户点按按钮时执行查询。这可确保 isFavourite 计算属性始终返回正确的值。
  private func toggleFavourite() {
    Task {
      if isFavourite {
        ...
      }

      let _ = try await isFavouriteRef.execute()
    }
  }

提取喜爱的电影

作为此功能的最后一步,您将实现提取用户喜爱的电影,以便他们在观看列表中看到这些电影。

  1. 在 VS Code 中,打开 dataconnect/connector/queries.gql 中的 queries.gql,然后粘贴以下查询:
## Get favorite movies by user ID
query GetUserFavoriteMovies @auth(level: USER) {
  user(id_expr: "auth.uid") {
    favoriteMovies: favorite_movies_on_user {
      movie {
        id
        title
        genre
        imageUrl
        releaseYear
        rating
        description
      }
    }
  }
}

用户的喜爱电影列表会显示在 LibraryScreen 上。只有在用户已登录的情况下,此界面才应显示数据,因此您需要先将界面的身份验证状态关联到应用的 AuthenticationService

  1. 添加代码以将 FavoriteMovieFavoriteMovies 映射到 Movie 再映射到 Movie+DataConnect.swift
import FirebaseDataConnect
import FriendlyFlixSDK

extension Movie {

  ...

  init(from: GetUserFavoriteMoviesQuery.Data.User.FavoriteMovieFavoriteMovies) {
    id = from.movie.id
    title = from.movie.title
    description = from.movie.description ?? ""
    releaseYear = from.movie.releaseYear
    rating = from.movie.rating
    imageUrl = from.movie.imageUrl
  }
}
  1. 在 Xcode 中,打开 LibraryScreen,然后按如下所示更新 isSignedIn
struct LibraryScreen: View {
  ...

  private var isSignedIn: Bool {
    authenticationService.user != nil
  }

}
  1. 然后,导入 Firebase Data Connect 和 FriendlyFlixSDK,并获取对 GetUserFavoriteMovies 查询的引用:
import SwiftUI
import FirebaseDataConnect
import FriendlyFlixSDK

struct LibraryScreen {

 ...

  private var connector = DataConnect.friendlyFlixConnector

  ...

  init() {
    watchListRef = connector.getUserFavoriteMoviesQuery.ref()
  }

  private let watchListRef: QueryRefObservation<
    GetUserFavoriteMoviesQuery.Data,
    GetUserFavoriteMoviesQuery.Variables
  >
  private var watchList: [Movie] {
    watchListRef.data?.user?.favoriteMovies.map(Movie.init) ?? []
  }

  ...

}


  1. 确保在视图显示时执行 watchListRef 查询:
extension LibraryScreen: View {
  var body: some View {
    ...
            MovieListSection(namespace: namespace, title: "Watch List", movies: watchList)
              .onAppear {
                Task {
                  try await watchListRef.execute()
                }
  ...

看看它的实际功效

现在,您可以运行应用并试用刚刚实现的“收藏夹”功能。请注意以下几点:

  • 确保 Firebase 模拟器正在运行
  • 确保您已为电影和电影详情添加模拟数据
  • 确保您已注册为用户
  1. 在 Xcode 中,点击 Run 按钮,在 iOS 模拟器上启动应用。
  2. 应用启动后,点按电影卡片即可显示电影详情。
  3. 点按心形图标即可将电影标记为收藏。心形图标应变为实心。
  4. 对几部电影重复此操作。
  5. 前往“媒体库”标签页。您现在应该会看到您标记为收藏的所有电影的列表。

10. 恭喜

恭喜!您已成功将 Firebase Data Connect 添加到 iOS 应用!现在,您已了解设置 Data Connect、创建查询和更改以及处理用户身份验证所需的关键步骤。

可选:部署到生产环境

到目前为止,此应用仅使用了 Firebase 模拟器。如果您想了解如何将此应用部署到真实的 Firebase 项目,请继续执行下一步。

11. (可选)部署应用

到目前为止,此应用完全在本地运行,所有数据都包含在 Firebase Emulator Suite 中。在本部分中,您将了解如何配置 Firebase 项目,以便此应用在生产环境中正常运行。

启用 Firebase Authentication

  1. 在 Firebase 控制台中,前往 Authentication 部分,然后点击 Get started(开始使用)。
  2. 前往登录方法标签页。
  3. 从原生提供程序部分中选择“电子邮件地址/密码”选项,
  4. 启用“电子邮件地址/密码”提供程序,然后点击保存

启用 Firebase Data Connect

重要提示:如果这是您首次在项目中部署架构,此过程将创建一个 Cloud SQL PostgreSQL 实例,可能需要大约 15 分钟。只有在 Cloud SQL 实例准备就绪并与 Firebase Data Connect 集成后,您才能进行部署。

1. 在 Firebase Data Connect VS Code 扩展程序界面中,点击部署到生产环境。2. 您可能需要审核架构更改并批准可能破坏性修改。系统会提示您执行以下操作:- 使用 firebase dataconnect:sql:diff 查看架构更改 - 对更改感到满意后,使用 firebase dataconnect:sql:migrate 启动的工作流程应用更改

您的 Cloud SQL for PostgreSQL 实例将更新为包含最终部署的架构和数据。您可以在 Firebase 控制台中监控状态。

现在,您可以点击 Firebase Data Connect 面板中的“Run(Production)”(运行 [生产]),就像在本地模拟器中一样,将数据添加到生产环境。

在再次运行 iOS 应用之前,请确保它已连接到项目的正式版实例:

  1. 打开 Product(产品)> Scheme(方案)> Edit Scheme…(修改方案…) 菜单。
  2. Run 部分,取消选中 -useEmulator YES 启动参数。