Firebase Data Connect を使用して構築する(iOS / Swift)

1. 概要

この Codelab では、Firebase Data Connect を Cloud SQL データベースに統合して、SwiftUI を使用して iOS 用の映画レビューアプリを構築するプロセスについて説明します。

このチュートリアルでは、Firebase Data Connect を使用して iOS アプリケーションを Cloud SQL データベースに接続し、映画レビューのシームレスなデータ同期を実現する方法について説明します。

この Codelab を完了すると、Firebase Data Connect の機能を使用して Cloud SQL データベースをバックエンドとする、ユーザーが映画を閲覧して映画をお気に入りとしてマークできる機能的な iOS アプリが完成します。

学習内容

この Codelab では、次の方法について学習します。

  • Firebase Emulator Suite を使用して、迅速なターンアラウンドを実現する Firebase Data Connect を設定します。
  • Data Connect と GraphQL を使用して、データベース スキーマを設計します。
  • データベース スキーマからタイプセーフな Swift SDK を作成し、Swift アプリケーションに追加します。
  • ユーザー認証を実装し、Firebase Data Connect と統合してユーザーデータを保護します。
  • GraphQL を利用したクエリとミューテーションを使用して、Cloud SQL のデータを取得、更新、削除、管理します。
  • (省略可)Data Connect サービスを本番環境にデプロイします。

前提条件

  • Xcode の最新バージョン
  • Codelab のサンプルコード。Codelab の最初のステップの 1 つで、サンプルコードをダウンロードします。

2. サンプル プロジェクトをセットアップする

Firebase プロジェクトを作成する

  1. Google アカウントを使用して Firebase コンソールにログインします。
  2. ボタンをクリックして新しいプロジェクトを作成し、プロジェクト名(例: Friendly Flix)を入力します。
  3. [続行] をクリックします。
  4. Firebase の利用規約が表示されたら、内容を読み、同意して [続行] をクリックします。
  5. (省略可)Firebase コンソールで AI アシスタンス(「Gemini in Firebase」)を有効にします。
  6. この Codelab では Google アナリティクスは必要ないため、Google アナリティクスのオプションをオフに切り替えます
  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 には、スキーマの管理、アプリケーションで使用するクエリとミューテーションの定義、厳密に型指定された SDK の生成に役立つツール(事前バンドルされた VS Code 拡張機能など)が用意されています。

スクリプトを実行すると、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. [Run firebase init] ボタンをクリックし、統合ターミナルの手順に沿って操作します。

SDK の生成を構成する

[Run firebase init] ボタンをクリックすると、Firebase Data Connect 拡張機能によって dataconnect ディレクトリが初期化されます。

VS Code で dataconnect/connector/connector.yaml ファイルを開くと、デフォルトの構成が表示されます。

構成を更新し、次の設定を使用して、生成されたコードがこの Codelab で動作することを確認してください。具体的には、connectorIdfriendly-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 Emulator が起動します。出力は次のようになります。

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

生成されたパッケージを Swift アプリに追加する

  1. Xcode で FriendlyFlix/app/FriendlyFlix/FriendlyFlix.xcodeproj を開く
  2. [ファイル] > [パッケージの依存関係の追加...] を選択します。
  3. [Add Local...] をクリックし、FriendlyFlix/app フォルダから FriendlyFlixSDK パッケージを追加します。
  4. Xcode がパッケージの依存関係を解決するまで待ちます。
  5. [Choose Package Products for FriendlyFlixSDK] ダイアログで、ターゲットとして FriendlyFlix を選択し、[Add Package] をクリックします。

ローカル エミュレータを使用するように iOS アプリを構成する

  1. FriendlyFlixApp.swift を開きます。(CMD+Shift+O キーを押して [Quick Open] ダイアログを開き、「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 で作成する複数のエンティティで構成されています。まずコア エンティティを作成し、機能を実装するたびに、その機能に必要なエンティティを追加します。

このステップでは、Movie タイプと MovieMetadata タイプを作成します。

映画

Movie 型は、titlegenrereleaseYearrating などのフィールドを含む、映画エンティティのメイン構造を定義します。

VS Code で、dataconnect/schema/schema.gqlMovie 型定義を追加します。

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 型との一対一の関係を確立します。映画の監督などの追加データが含まれます。

dataconnect/schema/schema.gql ファイルに MovieMetadata テーブル定義を追加します。

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

自動生成されるフィールドとデフォルト値

スキーマでは、@default(expr: "uuidV4()") などの式を使用して、一意の ID とタイムスタンプを自動的に生成します。たとえば、新しいレコードが作成されると、Movie 型の id フィールドに UUID が自動的に入力されます。

映画と映画のメタデータのモックデータを挿入する

スキーマを定義したら、テスト用のモックデータをデータベースに事前入力できます。

  1. Finder で、finish/FriendlyFlix/dataconnect/moviedata_insert.gqlstart/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 アプリなので、SwiftUI ビューの ID を取得するために 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) ?? []
  }

  ...
}

listMoviesQuery() クエリは、queries.gql を保存したときに Data Connect によって生成されました。Swift の実装については、FriendlyFlixSDK パッケージの FriendlyFlixOperations.swift ファイルをご覧ください。

アプリを実行する

Xcode で [実行] ボタンをクリックして、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. 映画と俳優の詳細を表示する

これで、映画をブラウジングできるようになりました。映画カードをタップすると、映画の詳細が表示されますが、詳細情報が不足していることに気づくかもしれません。

これは、映画のヒーロー セクションと上位の映画セクションをレンダリングするために必要なだけの詳細情報(映画のタイトル、簡単な説明、画像の URL)しか取得していないためです。

映画の詳細ページでは、映画に関する詳細情報を表示します。このセクションでは、詳細ページに映画の出演者とレビューを表示できるようにアプリを強化します。

そのため、次の 2 つの操作を行う必要があります。

  • 映画の俳優とレビューをサポートするようにスキーマを拡張する
  • 特定の映画の詳細を取得する 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.gqlstart/FriendlyFlix/dataconnect フォルダにコピーします。
  2. VS Code で dataconnect/moviededetails_insert.gql を開きます。
  3. Firebase Data Connect 拡張機能のエミュレータが実行されていることを確認します。
  4. ファイルの先頭に [Run (local)] ボタンが表示されます。これをクリックして、映画のモックデータをデータベースに挿入します。
  5. Data Connect Execution ターミナルで、データが正常に追加されたことを確認します。

データが配置されたら、次のステップに進んで、映画の詳細を取得するクエリを定義します。

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 で [実行] ボタンをクリックして、iOS シミュレータでアプリを起動します。

アプリが起動したら、映画カードをタップして映画の詳細を表示します。次のようになります。

8. ユーザー認証を実装する

現在、アプリにはパーソナライズされていない映画と俳優の情報が表示されます。次の手順では、データにログイン ユーザーを関連付ける機能を実装します。まず、ユーザーが映画を個人用の見たいものリストに追加できるようにします。

ウォッチリスト機能を実装する前に、まずユーザー ID を確立する必要があります。これを有効にするには、Firebase Authentication を統合して、ユーザーがアプリにログインできるようにします。

ホーム画面の右上にユーザー アバター ボタンが表示されていることに気づいた方もいるかもしれません。このボタンをタップすると、メールアドレスとパスワードを使用して登録またはログインできる画面が表示されます。

ユーザーが正常にログインしたら、アプリはユーザーの重要な詳細情報(主に一意のユーザー ID と選択したユーザー名)を保存する必要があります。

Firebase Authentication を有効にする

プロジェクトの Firebase コンソールで、[認証] セクションに移動し、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 を使用して、ユーザーがログインしたときに呼び出されるクロージャを登録できます。

そのクロージャ内で、データベースに新しいユーザー アカウントを作成できます。ただし、これを行う前に、データベースで新しいユーザーを作成または更新できるミューテーションを作成する必要があります。

スキーマに 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 で [実行] ボタンをクリックして、iOS シミュレータでアプリを起動します。
  • 画面右上のアバター アイコンをクリックします。
  • [登録] フローに切り替えて、アプリに登録します。

次に、データベースをクエリして、アプリがユーザーの新しいユーザー アカウントを作成したことを確認します。

  • VS Code で dataconnect/schema/schema.gql を開き、User エンティティの [データを読み取る] をクリックします。
  • これにより、User_read.gql という名前の新しいクエリ ファイルが作成されます。
  • [Run local] をクリックして、ユーザー テーブル内のすべてのユーザーを表示します。
  • [Data Connect Execution] ペインに、登録したユーザーのアカウントが表示されます。

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

お気に入りの追加と削除のミューテーションを定義する

アプリでユーザーのお気に入りの映画を表示するには、ユーザーがどれがお気に入りかを指定する必要があります。これを実現するには、まず、ユーザーのお気に入りの映画としてマークしたり、お気に入りから削除したりするための 2 つのミューテーションを追加する必要があります。

  1. VS Code で、dataconnect/connector/mutations.gqlmutations.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/connectorqueries.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.gqlqueries.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 から MovieMovie+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 で [実行] ボタンをクリックして、iOS シミュレータでアプリを起動します。
  2. アプリが起動したら、映画カードをタップして映画の詳細を表示します。
  3. ハートアイコンをタップして、映画をお気に入りとしてマークします。ハートが塗りつぶされます。
  4. この手順を数本の映画で繰り返します。
  5. [ライブラリ] タブに移動します。お気に入りとしてマークしたすべての映画の一覧が表示されます。

10. 完了

Firebase Data Connect が iOS アプリに正常に追加されました。これで、Data Connect の設定、クエリとミューテーションの作成、ユーザー認証の処理に必要な主な手順を学習できました。

省略可: 本番環境にデプロイする

これまでのところ、このアプリは Firebase エミュレータのみを使用しています。このアプリを実際の Firebase プロジェクトにデプロイする方法については、次のステップに進んでください。

11. (省略可)アプリをデプロイする

これまでのところ、このアプリは完全にローカルで、すべてのデータが Firebase Emulator Suite に含まれています。このセクションでは、このアプリが本番環境で動作するように Firebase プロジェクトを構成する方法について説明します。

Firebase Authentication を有効にする

  1. Firebase コンソールで [Authentication] セクションに移動し、[始める] をクリックします。
  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 で開始したフローを使用して変更を適用する

Cloud SQL for PostgreSQL インスタンスは、最終的にデプロイされたスキーマとデータで更新されます。ステータスは Firebase コンソールでモニタリングできます。

ローカル エミュレータの場合と同様に、Firebase Data Connect パネルで [Run (Production)] をクリックして、本番環境にデータを追加できるようになりました。

iOS アプリを再度実行する前に、プロジェクトのプロダクション インスタンスに接続されていることを確認します。

  1. [Product] > [Scheme] > [Edit Scheme] メニューを開きます。
  2. [実行] セクションで、-useEmulator YES 起動引数のチェックを外します。