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 에뮬레이터 도구 모음을 사용하여 빠른 처리 시간을 위해 Firebase Data Connect를 설정합니다.
  • 데이터 연결 및 GraphQL을 사용하여 데이터베이스 스키마를 설계합니다.
  • 데이터베이스 스키마에서 타입 안전 Swift SDK를 만들어 Swift 애플리케이션에 추가합니다.
  • 사용자 인증을 구현하고 Firebase Data Connect와 통합하여 사용자 데이터를 보호합니다.
  • GraphQL 기반의 쿼리 및 변형을 사용하여 Cloud SQL에서 데이터를 가져오고, 업데이트하고, 삭제하고, 관리합니다.
  • (선택사항) 데이터 연결 서비스를 프로덕션에 배포합니다.

기본 요건

  • 최신 버전의 Xcode
  • Codelab 샘플 코드 Codelab의 첫 번째 단계 중 하나에서 샘플 코드를 다운로드합니다.

2. 샘플 프로젝트 설정

Firebase 프로젝트 만들기

  1. Google 계정을 사용하여 Firebase Console에 로그인합니다.
  2. 버튼을 클릭하여 새 프로젝트를 만든 다음 프로젝트 이름 (예: Friendly Flix)을 입력합니다.
  3. 계속을 클릭합니다.
  4. 메시지가 표시되면 Firebase 약관을 검토하고 이에 동의한 다음 계속을 클릭합니다.
  5. (선택사항) Firebase Console에서 AI 지원('Firebase의 Gemini'라고 함)을 사용 설정합니다.
  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 Console의 왼쪽 탐색 메뉴에서 프로젝트 개요를 선택합니다.
  2. iOS+ 버튼을 클릭하여 플랫폼을 선택합니다. Apple 번들 ID를 입력하라는 메시지가 표시되면 com.google.firebase.samples.FriendlyFlix를 사용합니다.
  3. 앱 등록을 클릭하고 안내에 따라 GoogleServices-Info.plist 파일을 다운로드합니다.
  4. 다운로드한 파일을 방금 다운로드한 코드의 start/FriendlyFlix/app/FriendlyFlix/FriendlyFlix/ 디렉터리로 이동하여 기존 GoogleServices-Info.plist 파일을 바꿉니다.
  5. 그런 다음 다음을 몇 번 클릭하여 Firebase Console에서 설정 프로젝트를 완료합니다 (스타터 프로젝트에서 이미 처리되었으므로 앱에 SDK를 추가할 필요는 없음).
  6. 마지막으로 콘솔로 이동을 클릭하여 설정 프로세스를 완료합니다.

3. 데이터 연결 설정

설치

자동 설치

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 - 생성된 데이터 커넥트 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 인증이 로컬 에뮬레이터를 사용하는지 확인합니다.
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. 스키마 정의 및 데이터베이스 미리 채우기

이 섹션에서는 스키마에서 영화 애플리케이션의 주요 엔티티 간 구조와 관계를 정의합니다. Movie, MovieMetaData 등의 항목은 데이터베이스 테이블에 매핑되며, Firebase Data Connect 및 GraphQL 스키마 지시어를 사용하여 관계가 설정됩니다.

핵심 항목 및 관계

이 영화 추적기 앱의 데이터 모델은 이 Codelab에서 만드는 여러 엔티티로 구성됩니다. 먼저 핵심 항목을 만들고 기능을 더 많이 구현할수록 해당 기능에 필요한 항목을 추가합니다.

이 단계에서는 MovieMovieMetadata 유형을 만듭니다.

영화

Movie 유형은 title, genre, releaseYear, rating과 같은 필드를 포함하여 영화 항목의 기본 구조를 정의합니다.

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 유형과 일대일 관계를 설정합니다. 여기에는 영화 감독과 같은 추가 데이터가 포함됩니다.

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. 데이터 연결 실행 터미널을 확인하여 데이터가 성공적으로 추가되었는지 확인합니다.

데이터가 준비되면 다음 단계로 진행하여 데이터 커넥트에서 쿼리를 만드는 방법을 알아보세요.

5. 영화 가져오기 및 표시

이 섹션에서는 영화 목록을 표시하는 기능을 구현합니다.

먼저 movies 테이블에서 모든 영화를 가져오는 쿼리를 만드는 방법을 알아봅니다. Firebase Data Connect는 유형에 구애받지 않는 SDK의 코드를 생성하며, 이 코드를 사용하여 쿼리를 실행하고 검색된 영화를 앱의 UI에 표시할 수 있습니다.

ListMovies 쿼리 정의

Firebase Data Connect의 쿼리는 GraphQL로 작성되므로 가져올 필드를 지정할 수 있습니다. FriendlyFlix에서 영화를 표시하는 화면에는 title, description, releaseYear, rating, imageUrl 필드가 필요합니다. 또한 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
  }
}

새 쿼리를 테스트하려면 실행 (로컬) 버튼을 클릭하여 로컬 데이터베이스에 대해 쿼리를 실행합니다. 데이터베이스의 영화 목록이 데이터 커넥트 실행 터미널의 결과 섹션에 표시되어야 합니다.

ListMovies 쿼리를 앱의 홈 화면에 연결

이제 데이터 커넥트 에뮬레이터에서 쿼리를 테스트했으므로 앱 내부에서 쿼리를 호출할 수 있습니다.

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을 저장할 때 데이터 커넥터에 의해 생성되었습니다. 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. 데이터 연결 실행 터미널을 확인하여 데이터가 성공적으로 추가되었는지 확인합니다.

데이터가 준비되면 다음 단계로 진행하여 영화 세부정보를 가져오는 쿼리를 정의합니다.

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. 사용자 인증 구현

현재 앱은 맞춤설정되지 않은 영화 및 배우 정보를 표시합니다. 다음 단계에서는 데이터와 로그인한 사용자를 연결하는 기능을 구현합니다. 먼저 사용자가 개인 관심 목록에 영화를 추가할 수 있도록 허용합니다.

관심 목록 기능을 구현하려면 먼저 사용자 ID를 설정해야 합니다. 이를 사용 설정하려면 Firebase 인증을 통합하여 사용자가 앱에 로그인할 수 있도록 해야 합니다.

홈 화면 오른쪽 상단에 있는 사용자 아바타 버튼을 이미 확인하셨을 수도 있습니다. 이 버튼을 탭하면 사용자가 이메일과 비밀번호를 사용하여 가입하거나 로그인할 수 있는 화면으로 이동합니다.

사용자가 로그인하면 앱에서 사용자의 필수 세부정보(주로 고유 사용자 ID와 선택한 사용자 이름)를 저장해야 합니다.

Firebase 인증 사용 설정

프로젝트의 Firebase Console에서 인증 섹션으로 이동하여 Firebase 인증을 사용 설정합니다. 그런 다음 이메일/비밀번호 인증 제공업체를 사용 설정합니다.

로컬 프로젝트 폴더에서 firebase.json를 찾아 다음과 같이 업데이트하여 Firebase 인증 에뮬레이터를 사용 설정합니다.

{
  "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 유형은 사용자 항목을 정의합니다. 사용자는 리뷰를 남기거나 영화를 즐겨찾기에 추가하여 영화와 상호작용할 수 있습니다.

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 인증을 사용하여 가입할 때마다 자동으로 생성된 upsertUserMutation Firebase Data Connect를 사용하여 새 사용자를 삽입하거나 동일한 ID를 가진 기존 사용자를 업데이트합니다.

실제 사례 보기

이 기능이 작동하는지 확인하려면 먼저 iOS 앱에서 가입하세요.

  • 아직 실행하지 않았다면 중지하고 Firebase 에뮬레이터를 다시 시작하여 Firebase 인증 에뮬레이터가 실행되고 있는지 확인합니다.
  • Xcode에서 Run 버튼을 클릭하여 iOS 시뮬레이터에서 앱을 실행합니다.
  • 화면 오른쪽 상단에 있는 아바타 아이콘을 클릭합니다.
  • 가입 흐름으로 전환하고 앱에 가입합니다.

그런 다음 데이터베이스를 쿼리하여 앱이 사용자를 위한 새 사용자 계정을 만들었는지 확인합니다.

  • VS Code에서 dataconnect/schema/schema.gql를 열고 User 항목에서 데이터 읽기를 클릭합니다.
  • 그러면 User_read.gql라는 새 쿼리 파일이 생성됩니다.
  • Run local을 클릭하여 사용자 표에 모든 사용자를 표시합니다.
  • 이제 데이터 연결 실행 창에 방금 가입한 사용자의 계정이 표시됩니다.

9. 좋아하는 영화 관리

Codelab의 이 섹션에서는 영화 리뷰 앱에서 사용자 상호작용을 구현합니다. 특히 사용자가 즐겨찾기 영화를 관리할 수 있도록 합니다. 즐겨찾기로 표시된 영화는 앱의 관심 목록 섹션에 표시됩니다.

즐겨찾기를 지원하도록 스키마 개선

FavoriteMovie 유형은 사용자와 사용자가 좋아하는 영화 간의 다대다 관계를 처리하는 조인 테이블입니다. 각 테이블은 UserMovie에 연결합니다.

코드 스니펫을 복사하여 dataconnect/schema/schema.gql 파일에 붙여넣습니다.

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

즐겨찾기 추가 및 삭제를 위한 변이 정의

앱에서 사용자가 좋아하는 영화를 표시하려면 사용자가 좋아하는 영화를 지정해야 합니다. 이를 위해 먼저 두 가지 변형을 추가하여 영화를 사용자의 즐겨찾기로 표시하거나 즐겨찾기에서 다시 삭제해야 합니다.

  1. VS Code에서 dataconnect/connector/mutations.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+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. 축하합니다

수고하셨습니다. iOS 앱에 Firebase Data Connect를 추가했습니다. 이제 데이터 커넥트를 설정하고, 쿼리와 변형을 만들고, 사용자 인증을 처리하는 데 필요한 주요 단계를 알게 되었습니다.

선택사항: 프로덕션에 배포

지금까지 이 앱은 Firebase 에뮬레이터만 사용했습니다. 이 앱을 실제 Firebase 프로젝트에 배포하는 방법을 알아보려면 다음 단계로 계속 진행하세요.

11. (선택사항) 앱 배포

지금까지 이 앱은 완전히 로컬이었으며 모든 데이터가 Firebase 에뮬레이터 도구 모음에 포함되어 있습니다. 이 섹션에서는 이 앱이 프로덕션에서 작동하도록 Firebase 프로젝트를 구성하는 방법을 알아봅니다.

Firebase 인증 사용 설정

  1. Firebase Console에서 인증 섹션으로 이동하여 시작하기를 클릭합니다.
  2. 로그인 방법 탭으로 이동합니다 .
  3. 기본 제공업체 섹션에서 이메일/비밀번호 옵션을 선택합니다.
  4. 이메일/비밀번호 제공업체를 사용 설정한 다음 저장을 클릭합니다.

Firebase Data Connect 사용 설정

중요: 프로젝트에서 스키마를 처음 배포하는 경우 이 프로세스에서 Cloud SQL PostgreSQL 인스턴스를 만듭니다. 이 작업은 약 15분 정도 걸릴 수 있습니다. Cloud SQL 인스턴스가 준비되고 Firebase 데이터 연결과 통합될 때까지는 배포할 수 없습니다.

1. Firebase Data Connect VS Code 확장 프로그램 UI에서 프로덕션에 배포를 클릭합니다. 2. 스키마 변경사항을 검토하고 파괴적인 수정사항을 승인해야 할 수도 있습니다. 다음과 같은 메시지가 표시됩니다. - firebase dataconnect:sql:diff를 사용하여 스키마 변경사항 검토 - 변경사항이 만족스러우면 firebase dataconnect:sql:migrate로 시작된 흐름을 사용하여 변경사항 적용

PostgreSQL용 Cloud SQL 인스턴스가 최종 배포된 스키마와 데이터로 업데이트됩니다. Firebase Console에서 상태를 모니터링할 수 있습니다.

이제 로컬 에뮬레이터에서와 마찬가지로 Firebase 데이터 커넥트 패널에서 실행 (프로덕션)을 클릭하여 프로덕션 환경에 데이터를 추가할 수 있습니다.

iOS 앱을 다시 실행하기 전에 프로젝트의 프로덕션 인스턴스에 연결되어 있는지 확인하세요.

  1. Product(제품) > Scheme(스키마) > Edit Scheme(스키마 수정) 메뉴를 엽니다.
  2. 실행 섹션에서 -useEmulator YES 실행 인수를 선택 해제합니다.