Firebase Data Connect로 빌드 (iOS / Swift)

1. 개요

이 Codelab에서는 Firebase 데이터 연결을 Cloud SQL 데이터베이스와 통합하여 SwiftUI를 사용하여 iOS용 영화 리뷰 앱을 빌드하는 프로세스를 안내합니다.

Firebase 데이터 연결을 사용하여 iOS 애플리케이션을 Cloud SQL 데이터베이스에 연결하여 영화 리뷰의 원활한 데이터 동기화를 지원하는 방법을 알아봅니다.

이 Codelab을 마치면 사용자가 영화를 둘러보고 영화를 즐겨찾기로 표시할 수 있는 작동하는 iOS 앱을 갖게 됩니다. 이 앱은 모두 Firebase 데이터 연결의 기능을 사용하여 Cloud SQL 데이터베이스를 기반으로 합니다.

학습할 내용

이 Codelab에서는 다음을 수행하는 방법을 알아봅니다.

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

기본 요건

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

2. 샘플 프로젝트 설정

Firebase 프로젝트 만들기

  1. Google 계정으로 Firebase Console에 로그인합니다.
  2. Firebase Console에서 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 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. 마지막으로 Console로 이동을 클릭하여 설정 프로세스를 완료합니다.

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 프로젝트 연결 버튼을 클릭하고 이전에 Console에서 만든 프로젝트를 선택합니다.
  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 에뮬레이터가 시작됩니다. 출력은 다음과 같습니다.

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 인증, 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. 스키마 정의 및 데이터베이스 미리 채우기

이 섹션에서는 영화 애플리케이션의 핵심 항목 간의 구조와 관계를 스키마로 정의합니다. Movie, MovieMetaData 등의 항목은 Firebase Data Connect 및 GraphQL 스키마 디렉티브를 사용하여 관계가 설정된 데이터베이스 테이블에 매핑됩니다.

핵심 항목 및 관계

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

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

영화

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

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에서 영화를 표시하는 화면에는 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
  }
}

새 쿼리를 테스트하려면 Run (local) 버튼을 클릭하여 로컬 데이터베이스에 대해 쿼리를 실행합니다. 데이터베이스의 영화 목록이 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 실행 터미널에서 데이터가 성공적으로 추가되었는지 확인합니다.

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

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라는 새 쿼리 파일이 생성됩니다.
  • 로컬 실행을 클릭하여 사용자 표에 있는 모든 사용자를 확인합니다.
  • 이제 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!
}

즐겨찾기 추가 및 삭제에 관한 변형 정의

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

  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.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에서 Run 버튼을 클릭하여 iOS 시뮬레이터에서 앱을 실행합니다.
  2. 앱이 실행되면 영화 카드를 탭하여 영화 세부정보를 표시합니다.
  3. 하트 아이콘을 탭하여 영화를 즐겨찾기로 표시합니다. 하트가 채워집니다.
  4. 이 작업을 몇 편의 영화에 대해 반복합니다.
  5. 보관함 탭으로 이동합니다. 이제 즐겨찾기로 표시한 모든 영화 목록이 표시됩니다.

10. 축하합니다

수고하셨습니다. Firebase Data Connect를 iOS 앱에 추가했습니다. 이제 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 Data Connect 패널에서 Run(실행)(프로덕션)을 클릭하여 프로덕션 환경에 데이터를 추가할 수 있습니다.

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

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