運用 Firebase Data Connect 建構內容 (iOS / Swift)

1. 總覽

本程式碼研究室將逐步說明如何整合 Firebase Data Connect 與 Cloud SQL 資料庫,使用 SwiftUI 建構 iOS 電影評論應用程式

您將瞭解如何使用 Firebase Data Connect 將 iOS 應用程式連線至 Cloud SQL 資料庫,順暢同步電影評論資料。

完成本程式碼研究室後,您將擁有可正常運作的 iOS 應用程式,使用者可以瀏覽電影並將電影標示為最愛,所有資料都由 Cloud SQL 資料庫提供,並透過 Firebase Data Connect 支援。

課程內容

本程式碼研究室會說明如何:

  • 設定 Firebase Data Connect,並使用 Firebase Emulator Suite 縮短周轉時間。
  • 使用 Data Connect 和 GraphQL 設計資料庫結構定義
  • 從資料庫結構定義建立型別安全 Swift SDK,並新增至 Swift 應用程式。
  • 實作使用者驗證並與 Firebase Data Connect 整合,確保使用者資料安全無虞。
  • 使用 GraphQL 支援的查詢和變異,在 Cloud SQL 中擷取、更新、刪除及管理資料。
  • (選用) 將 Data Connect 服務部署至正式環境。

事前準備

  • 最新版 Xcode
  • 程式碼研究室範例程式碼。您會在程式碼研究室的第一個步驟中下載範例程式碼。

2. 設定範例專案

建立 Firebase 專案

  1. 使用 Google 帳戶登入 Firebase 控制台
  2. 按一下按鈕建立新專案,然後輸入專案名稱 (例如 Friendly Flix)。
  3. 按一下「繼續」
  4. 如果系統提示,請詳閱並接受 Firebase 條款,然後按一下「繼續」
  5. (選用) 在 Firebase 控制台中啟用 AI 輔助功能 (稱為「Gemini in Firebase」)。
  6. 本程式碼研究室不需要 Google Analytics,因此請關閉 Google Analytics 選項。
  7. 按一下「建立專案」,等待專案佈建完成,然後按一下「繼續」

下載程式碼

執行下列指令,複製這個程式碼研究室的範例程式碼。這會在您的機器上建立名為 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. 最後,按一下「Continue to console」(繼續前往控制台),完成設定程序。

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 擴充功能 UI

  1. 按一下「使用 Google 帳戶登入」按鈕。瀏覽器視窗隨即開啟,請按照操作說明使用 Google 帳戶登入擴充功能。
  2. 按一下「連結 Firebase 專案」按鈕,然後選取您先前在控制台中建立的專案。
  3. 按一下「執行 firebase init」按鈕,然後按照整合式終端機中的步驟操作。

設定 SDK 產生作業

按一下「Run firebase init」按鈕後,Firebase Data Connect 擴充功能應會為您初始化 dataconnect 目錄。

在 VS Code 中開啟 dataconnect/connector/connector.yaml 檔案,您會看到預設設定。

請更新設定,並使用下列設定,確保產生的程式碼適用於本程式碼研究室。具體來說,請確認 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. 在「Destination」(目的地) 下拉式選單中選取 iOS 模擬器。
  2. 在 Xcode 中按下 CMD+R 鍵 (或點選「Run」按鈕),即可在模擬器上執行應用程式。

4. 定義結構定義並預先填入資料庫

在本節中,您將在結構定義中定義電影應用程式中主要實體之間的結構和關係。MovieMovieMetaData 等實體會對應至資料庫表格,並透過 Firebase Data Connect 和 GraphQL 結構定義指令建立關係。

核心實體和關係

這部電影追蹤器應用程式的資料模型包含多個實體,您會在完成本程式碼研究室的過程中建立這些實體。您會先建立核心實體,然後隨著實作越來越多功能,新增這些功能所需的實體。

在這個步驟中,您將建立 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 的程式碼,您可以使用該程式碼執行查詢,並在應用程式的 UI 中顯示擷取的電影。

定義 ListMovies 查詢

Firebase Data Connect 中的查詢是以 GraphQL 編寫,可讓您指定要擷取的欄位。在 FriendlyFlix 中,顯示電影的畫面需要下列欄位:titledescriptionreleaseYearratingimageUrl。此外,由於這是 SwiftUI 應用程式,您需要 id 協助處理 SwiftUI 檢視區塊 ID。

在 VS Code 中開啟 dataconnect/connector/queries.gql 檔案,並新增 ListMovies 查詢:

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

如要測試新查詢,請按一下「執行 (本機)」按鈕,對本機資料庫執行查詢。資料庫中的電影清單應會顯示在 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) ?? []
  }

  ...
}

儲存 queries.gql 時,資料連結會產生 listMoviesQuery() 查詢。如要查看 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 中的程式碼,以使用這些額外功能。

在 UI 中使用強化查詢

返回 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
      }
  }
}

最後,更新將這項查詢結果連結至 UI 的計算屬性:

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. 在 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. 檢查資料連結執行終端機,確認資料已成功新增。

資料就位後,請繼續下一個步驟,定義查詢來擷取電影詳細資料。

定義 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 驗證,讓使用者登入應用程式。

你可能已經在主畫面右上角看到使用者顯示圖片按鈕。輕觸這個按鈕後,畫面會顯示電子郵件地址和密碼欄位,供使用者註冊或登入。

使用者成功登入後,應用程式需要儲存他們的基本詳細資料,主要是專屬使用者 ID 和所選使用者名稱。

啟用 Firebase 驗證

在專案的 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 註冊閉包,在使用者登入時呼叫。

在該閉包中,您可以在資料庫中建立新的使用者帳戶。不過,您必須先建立變動,才能在資料庫中建立或更新新使用者。

在結構定義中新增 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 驗證模擬器正在執行。
  • 在 Xcode 中,按一下「Run」按鈕,在 iOS 模擬器上啟動應用程式。
  • 按一下畫面右上角的顯示圖片圖示。
  • 切換至「註冊」流程,並註冊應用程式。

接著,查詢資料庫,確認應用程式已為使用者建立新的使用者帳戶:

  • 在 VS Code 中開啟 dataconnect/schema/schema.gql,然後點選 User 實體上的「Read data」(讀取資料)
  • 這會建立名為 User_read.gql 的新查詢檔案
  • 按一下「執行本機」,即可查看使用者表格中的所有使用者
  • 在「資料連結執行」窗格中,您現在應該會看到剛才註冊的使用者帳戶

9. 管理喜愛的電影

在本程式碼研究室的這一節中,您將在電影評論應用程式中實作使用者互動,具體來說,就是讓使用者管理自己喜愛的電影。標示為最愛的電影會顯示在應用程式的待觀看清單專區。

強化結構定義以支援收藏功能

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!
}

定義用於新增及移除收藏項目的突變

應用程式必須先知道使用者喜愛的電影,才能顯示相關內容。如要達成這個目標,您必須先新增兩個突變,分別將電影標示為使用者的最愛,或從最愛中移除。

  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 })
}

將突變連結至應用程式的 UI

使用者只要在電影的詳細資料畫面中點選愛心圖示,即可將電影標示為我的最愛。

如要將剛建立的突變項目連結至應用程式的 UI,請在 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)
      }
    }
  }
}

這會更新資料庫中目前電影的收藏狀態。最後一個步驟是確保 UI 狀態會相應反映。

定義查詢,判斷電影是否標示為「我的最愛」

  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 應用程式!您現在已瞭解設定資料連線、建立查詢和變動,以及處理使用者驗證時,必須採取的關鍵步驟。

(選用) 部署至正式環境

目前這個應用程式只使用 Firebase 模擬器。如要瞭解如何將這個應用程式部署至實際的 Firebase 專案,請繼續進行下一個步驟。

11. (選用) 部署應用程式

到目前為止,這個應用程式完全在本機執行,所有資料都包含在 Firebase 模擬器套件中。在本節中,您將瞭解如何設定 Firebase 專案,讓這個應用程式在正式環境中運作。

啟用 Firebase 驗證

  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 擴充功能 UI 中,按一下「Deploy to production」。2. 您可能需要檢查結構定義變更,並核准可能造成破壞性影響的修改。系統會提示您:- 使用 firebase dataconnect:sql:diff 查看結構定義變更 - 滿意變更內容後,使用 firebase dataconnect:sql:migrate 啟動的流程套用變更

系統會使用最終部署的結構定義和資料更新 PostgreSQL 適用的 Cloud SQL 執行個體。您可以在 Firebase 控制台中監控狀態。

現在,您可以在 Firebase 資料連線面板中按一下「執行 (正式環境)」,就像使用本機模擬器一樣,將資料新增至正式環境。

再次執行 iOS 應用程式前,請確認應用程式已連線至專案的正式版例項:

  1. 開啟「Product」>「Scheme」>「Edit Scheme...」選單。
  2. 在「執行」部分,取消勾選 -useEmulator YES 啟動引數。