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 アナリティクスを有効にするよう求められる場合があります。この Codelab では、どちらを選択してもかまいません。
  6. 約 1 分後に 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 が追加されているため、アプリに 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 拡張機能の 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. [File] > [Add Package Dependencies...] を選択します。
  3. [ローカルを追加...] をクリックし、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 キーを押すか、[実行] ボタンをクリックして、シミュレータでアプリを実行します。

4. スキーマを定義してデータベースにデータを事前入力する

このセクションでは、映画アプリの主要なエンティティの構造と関係をスキーマで定義します。MovieMovieMetaData などのエンティティはデータベース テーブルにマッピングされ、Firebase Data Connect と GraphQL スキーマ ディレクティブを使用して関係が確立されます。

コア エンティティと関係

この映画トラッカー アプリのデータモデルは、この Codelab で作成する複数のエンティティで構成されています。まずコア エンティティを作成し、機能を実装するにつれて、それらの機能に必要なエンティティを追加します。

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

映画

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

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.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 で [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. 映画と俳優の詳細を表示する

これで、映画をブラウジングできるようになります。映画カードをタップすると、映画の詳細が表示されますが、その詳細情報には、ある程度の詳細情報が欠落しているように感じるかもしれません。

これは、映画のヒーロー セクションとトップ ムービー セクションのレンダリングに必要な各映画の詳細(映画のタイトル、簡単な説明、画像の URL)のみを取得したためです。

映画の詳細ページには、映画の詳細情報を表示します。このセクションでは、映画の俳優とレビューを詳細ページに表示できるようにアプリを拡張します。

そのためには、次の手順を行う必要があります。

  • 映画の俳優とレビューをサポートするようにスキーマを拡張
  • 特定の映画の詳細を取得する 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 Emulator を停止して再起動する必要があります。

認証ハンドラを実装する

次のセクションでは、ユーザー認証とデータベースを接続するロジックを実装します。これには、ログインが成功したことをリッスンする認証ハンドラの作成が含まれます。

ユーザーが認証されると、このハンドラはデータベース内の対応するアカウントの作成を自動的にトリガーします。

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 タイプはユーザー エンティティを定義します。ユーザーは、レビューを投稿したり、映画を [お気に入り] に追加したりして、映画を操作できます。

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 という名前の新しいクエリ ファイルが作成されます。
  • [ローカルで実行] をクリックして、ユーザー表にすべてのユーザーを表示します。
  • [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!
}

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

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

  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 から Movie に、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 で [実行] ボタンをクリックして、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 パネルで [実行(本番環境)] をクリックして、本番環境にデータを追加できます。

iOS アプリを再度実行する前に、プロジェクトの本番環境インスタンスに接続していることを確認します。

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