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

1. 概览

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

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

完成此 Codelab 后,您将获得一个功能完善的 iOS 应用,该应用可让用户浏览电影并将电影标记为喜爱,所有这些功能都由 Cloud SQL 数据库提供支持,并借助 Firebase Data Connect 的强大功能实现。

学习内容

此 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. 点击相应按钮以创建新项目,然后输入项目名称(例如 Friendly Flix)。
  3. 点击继续
  4. 如果看到相关提示,请查看并接受 Firebase 条款,然后点击继续
  5. (可选)在 Firebase 控制台中启用 AI 辅助功能(称为“Gemini in Firebase”)。
  6. 在此 Codelab 中,您不需要使用 Google Analytics,因此请关闭 Google Analytics 选项。
  7. 点击创建项目,等待项目完成预配,然后点击继续

下载代码

运行以下命令以克隆本 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 视图,然后点击启动模拟器按钮。

这将在集成终端中启动 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(或点击运行按钮),以在模拟器上运行应用。

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 和时间戳。例如,创建新记录时,系统会自动使用 UUID 填充 Movie 类型中的 id 字段。

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

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

  1. 在“访达”中,将 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
  }
}

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

将 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"
}

为演员添加模拟数据

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

  1. 在“访达”中,将 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 控制台中,前往“身份验证”部分并启用 Firebase 身份验证。然后,启用电子邮件地址/密码身份验证提供方。

在本地项目文件夹中,找到 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)")
}

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

在 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 {
    ...
  }
}

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

实例展示

如需验证此功能是否正常运行,请先在 iOS 应用中注册:

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

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

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

9. 管理喜爱的电影

在此 Codelab 的这一部分中,您将在电影评价应用中实现用户互动,具体来说,就是让用户管理自己喜爱的电影。标记为喜爱的电影将显示在应用的观看列表部分。

增强架构以支持收藏夹

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

将代码段复制并粘贴到 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!
}

定义用于添加和移除收藏的 mutation

在应用可以显示用户喜爱的电影之前,用户需要指明哪些电影是自己喜爱的。为此,您首先需要添加两个突变,分别用于将电影标记为用户的喜爱电影,或将其从喜爱电影中移除。

  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 控制台中,前往身份验证部分,然后点击开始
  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 面板中点击“运行(生产)”,将数据添加到生产环境中。

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

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