Сборка с помощью Firebase Data Connect (iOS/Swift)

1. Обзор

В этой лабораторной работе вы узнаете, как интегрировать Firebase Data Connect с базой данных Cloud SQL для создания приложения для просмотра фильмов на iOS с использованием SwiftUI.

Вы узнаете, как подключить свое iOS-приложение к базе данных Cloud SQL с помощью Firebase Data Connect, что обеспечит бесперебойную синхронизацию данных для обзоров фильмов.

К концу этой лабораторной работы у вас будет функциональное приложение для iOS, позволяющее пользователям просматривать фильмы и добавлять их в избранное. Все это будет работать на базе базы данных Cloud SQL с использованием возможностей Firebase Data Connect.

Чему вы научитесь

В этой лабораторной работе вы научитесь:

  • Настройте Firebase Data Connect, используя набор эмуляторов Firebase, чтобы ускорить обработку данных.
  • Разработайте схему базы данных с использованием Data Connect и GraphQL .
  • Создайте типобезопасный Swift SDK из схемы вашей базы данных и добавьте его в приложение Swift.
  • Реализуйте аутентификацию пользователей и интегрируйте ее с Firebase Data Connect, чтобы защитить данные ваших пользователей.
  • Извлекайте, обновляйте, удаляйте и управляйте данными в Cloud SQL с помощью запросов и мутаций на основе GraphQL.
  • (Необязательно) Разверните службу Data Connect в рабочей среде.

Предпосылки

  • Последняя версия Xcode
  • Пример кода для практического занятия. Вы загрузите пример кода на одном из первых этапов практического занятия.

2. Настройте пример проекта

Создать проект Firebase

  1. Войдите в консоль Firebase, используя свою учетную запись Google.
  2. Нажмите кнопку, чтобы создать новый проект, а затем введите название проекта (например, Friendly Flix ).
  3. Нажмите «Продолжить» .
  4. При появлении соответствующего запроса ознакомьтесь с условиями Firebase и примите их, а затем нажмите кнопку «Продолжить» .
  5. (Необязательно) Включите помощь ИИ в консоли Firebase (так называемая «Gemini в Firebase»).
  6. Для этой лабораторной работы вам не понадобится Google Analytics, поэтому отключите опцию Google Analytics.
  7. Нажмите «Создать проект» , дождитесь завершения подготовки проекта, а затем нажмите «Продолжить» .

Загрузите код

Выполните следующую команду, чтобы клонировать пример кода для этой лабораторной работы. Это создаст на вашем компьютере каталог с именем codelab-dataconnect-ios :

git clone https://github.com/FirebaseExtended/codelab-dataconnect-ios`

Если на вашем компьютере нет git, вы также можете загрузить код непосредственно с GitHub.

Добавить конфигурацию Firebase

Firebase SDK использует файл конфигурации для подключения к вашему проекту Firebase. На платформах Apple этот файл называется GoogleServices-Info.plist . На этом этапе вам нужно скачать файл конфигурации и добавить его в свой проект Xcode.

  1. В консоли Firebase выберите Обзор проекта в левой навигационной панели.
  2. Нажмите кнопку iOS+ , чтобы выбрать платформу. При запросе идентификатора пакета Apple используйте com.google.firebase.samples.FriendlyFlix
  3. Нажмите «Зарегистрировать приложение» и следуйте инструкциям по загрузке файла GoogleServices-Info.plist .
  4. Переместите загруженный файл в каталог start/FriendlyFlix/app/FriendlyFlix/FriendlyFlix/ кода, который вы только что скачали, заменив существующий файл GoogleServices-Info.plist .
  5. Затем нажмите кнопку «Далее» несколько раз, чтобы завершить проект по настройке в консоли Firebase (вам не нужно добавлять 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. Установите расширение Firebase Data Connect из Visual Studio Code Marketplace .

Инициализируйте Data Connect в проекте

На левой панели щелкните значок Firebase, чтобы открыть пользовательский интерфейс расширения Data Connect VS Code.

  1. Нажмите кнопку «Войти через Google» . Откроется окно браузера; следуйте инструкциям, чтобы войти в расширение, используя свою учётную запись Google.
  2. Нажмите кнопку Подключить проект Firebase и выберите проект, который вы создали ранее в консоли.
  3. Нажмите кнопку «Запустить firebase init» и следуйте инструкциям во встроенном терминале.

Настроить генерацию SDK

После нажатия кнопки «Run firebase init» расширение Firebase Data Connect должно инициализировать для вас каталог dataconnect .

В VS Code откройте файл dataconnect/connector/connector.yaml , и вы найдете конфигурацию по умолчанию.

Обновите конфигурацию и используйте следующие настройки, чтобы сгенерированный код работал с этой лабораторной работой. В частности, убедитесь, что для параметра connectorId задано значение friendly-flix , а для пакета Swift — FriendlyFlixSDK .

connectorId: "friendly-flix"
generate:
  swiftSdk:
    outputDir: "../../app"
    package: "FriendlyFlixSDK"
    observablePublisher: observableMacro

Вот что означают эти настройки:

  • connectorId — уникальное имя данного коннектора.
  • outputDir — путь к месту сохранения сгенерированного Data Connect SDK. Этот путь указывается относительно каталога, содержащего файл connector.yaml .
  • package — имя пакета, которое будет использоваться для сгенерированного пакета Swift.

После сохранения этого файла Firebase Data Connect сгенерирует для вас пакет Swift с именем FriendlyFlixSDK и поместит его рядом с папкой проекта FriendlyFlix .

Запустите эмуляторы Firebase

В VS Code переключитесь на представление Firebase , а затем нажмите кнопку Запустить эмуляторы .

Это запустит эмулятор Firebase во встроенном терминале. Вывод должен выглядеть примерно так:

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

Добавьте сгенерированный пакет в ваше приложение Swift.

  1. Откройте FriendlyFlix/app/FriendlyFlix/FriendlyFlix.xcodeproj в Xcode.
  2. Выберите Файл > Добавить зависимости пакета...
  3. Нажмите «Добавить локальный...» , затем добавьте пакет FriendlyFlixSDK из папки FriendlyFlix/app
  4. Подождите, пока Xcode разрешит зависимости пакетов.
  5. В диалоговом окне «Выбор продуктов пакета для FriendlyFlixSDK» выберите FriendlyFlix в качестве целевого объекта и нажмите «Добавить пакет» .

Настройте приложение 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. В раскрывающемся списке «Назначение» выберите симулятор iOS.
  2. Нажмите CMD+R (или щелкните кнопку « Выполнить ») в Xcode, чтобы запустить приложение на симуляторе.

4. Определите схему и предварительно заполните базу данных.

В этом разделе вы определите структуру и связи между ключевыми сущностями приложения для просмотра фильмов в схеме. Такие сущности, как Movie , MovieMetaData и другие, сопоставляются с таблицами базы данных, а связи устанавливаются с помощью директив схемы Firebase Data Connect и GraphQL.

Основные сущности и отношения

Модель данных для этого приложения для отслеживания фильмов состоит из нескольких сущностей, которые вы создадите в ходе этой лабораторной работы. Сначала вы создадите основные сущности, а затем, по мере реализации новых функций, будете добавлять необходимые для них сущности.

На этом этапе вы создадите типы Movie и MovieMetadata .

Фильм

Тип 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 . Он включает дополнительные данные, например, данные о режиссёре фильма.

Добавьте определение таблицы MovieMetadata в файл dataconnect/schema/schema.gql :

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

Автоматически сгенерированные поля и значения по умолчанию

Схема использует выражения вроде @default(expr: "uuidV4()") для автоматической генерации уникальных идентификаторов и временных меток. Например, поле id в типе Movie автоматически заполняется UUID при создании новой записи.

Вставьте фиктивные данные для фильмов и метаданных фильмов

Определив схему, вы теперь можете предварительно заполнить базу данных фиктивными данными для тестирования.

  1. В Finder скопируйте finish/FriendlyFlix/dataconnect/moviedata_insert.gql в папку start/FriendlyFlix/dataconnect .
  2. В VS Code откройте dataconnect/moviedata_insert.gql .
  3. Убедитесь, что эмуляторы в расширении Firebase Data Connect запущены.
  4. В верхней части файла должна появиться кнопка «Запустить (локально)» . Нажмите её, чтобы вставить данные макета фильма в базу данных.
  5. Проверьте терминал Data Connect Execution, чтобы убедиться, что данные были успешно добавлены.

Имея данные на месте, перейдите к следующему шагу, чтобы научиться создавать запросы в Data Connect.

5. Извлечение и отображение фильмов

В этом разделе вы реализуете функцию отображения списка фильмов.

Сначала вы узнаете, как создать запрос, который извлекает все фильмы из таблицы movies . Firebase Data Connect генерирует код для типобезопасного SDK, который затем можно использовать для выполнения запроса и отображения извлеченных фильмов в пользовательском интерфейсе вашего приложения.

Определить запрос ListMovies

Запросы в Firebase Data Connect пишутся на языке GraphQL, что позволяет указать, какие поля следует извлекать. В FriendlyFlix экраны, на которых отображаются фильмы, должны содержать следующие поля: title , description , releaseYear , rating и imageUrl . Кроме того, поскольку это приложение SwiftUI, вам понадобится id для идентификации представления SwiftUI.

В VS Code откройте файл dataconnect/connector/queries.gql и добавьте запрос ListMovies :

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

Чтобы протестировать новый запрос, нажмите кнопку «Выполнить (локально)» , чтобы выполнить его к локальной базе данных. Список фильмов из базы данных должен отобразиться в разделе «Результаты» терминала Data Connect Execution.

Подключите запрос ListMovies к главному экрану приложения.

Теперь, когда вы протестировали запрос в эмуляторе Data Connect, вы можете вызвать запрос из своего приложения.

При сохранении queries.gql Firebase Data Connect генерирует код, соответствующий запросу ListMovies в пакете FriendlyFlixSDK .

В 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() был сгенерирован Data Connect при сохранении queries.gql . Чтобы увидеть его реализацию на Swift, ознакомьтесь с файлом FriendlyFlixOperations.swift в пакете FriendlyFlixSDK .

Запустите приложение

В Xcode нажмите кнопку «Запустить» , чтобы запустить приложение в iOS Simulator.

После запуска приложения вы увидите примерно такой экран:

Вы могли заметить, что во всех разделах приложения (раздел «Главные герои», «Лучшие фильмы» и «Список просмотра») отображается один и тот же список. Это связано с тем, что вы используете один и тот же запрос для всех этих представлений. В следующих разделах вы реализуете пользовательские запросы.

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 , чтобы использовать эти дополнительные функции.

Используйте расширенный запрос в пользовательском интерфейсе

Вернитесь в 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
      }
  }
}

Наконец, обновите вычисляемое свойство, которое связывает результат этого запроса с пользовательским интерфейсом:

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 и добавьте определения схемы для Actor и MovieActor .

## Actors
## An actor can participate in multiple movies; movies can have multiple actors
## Movie - Actors (or vice versa) is a many to many relationship
type Actor @table {
  id: UUID!
  imageUrl: String!
  name: String! @col(name: "name", dataType: "varchar(30)")
}

## Join table for many-to-many relationship for movies and actors
## The 'key' param signifies the primary key(s) of this table
## In this case, the keys are [movieId, actorId], the generated fields of the reference types [movie, actor]
type MovieActor @table(key: ["movie", "actor"]) {
  ## @ref creates a field in the current table (MovieActor) that holds the primary key of the referenced type
  ## In this case, @ref(fields: "id") is implied
  movie: Movie!
  ## movieId: UUID! <- this is created by the implied @ref, see: implicit.gql

  actor: Actor!
  ## actorId: UUID! <- this is created by the implied  @ref, see: implicit.gql

  role: String! ## "main" or "supporting"
}

Добавить фиктивные данные для актеров

После обновления схемы вы теперь можете заполнить базу данных дополнительными тестовыми данными для тестирования.

  1. В Finder скопируйте finish/FriendlyFlix/dataconnect/moviededetails_insert.gql в папку start/FriendlyFlix/dataconnect .
  2. В VS Code откройте dataconnect/moviededetails_insert.gql .
  3. Убедитесь, что эмуляторы в расширении Firebase Data Connect запущены.
  4. В верхней части файла должна появиться кнопка «Запустить (локально)» . Нажмите её, чтобы вставить данные макета фильма в базу данных.
  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 Simulator.

После запуска приложения нажмите на карточку фильма, чтобы отобразить информацию о нём. Она должна выглядеть примерно так:

8. Реализуйте аутентификацию пользователей.

В настоящее время приложение предоставляет неперсонализированную информацию о фильмах и актёрах. На следующих этапах вы реализуете функции, связывающие данные с вошедшим в систему пользователем. Сначала вы позволите пользователям добавлять фильмы в свой личный список просмотра.

Прежде чем реализовать функцию списка наблюдения, необходимо установить личность пользователя. Для этого необходимо интегрировать аутентификацию Firebase, которая позволит пользователям входить в приложение.

Возможно, вы уже заметили кнопку аватара пользователя в правом верхнем углу главного экрана. Нажав на неё, вы попадёте на экран, где пользователи могут зарегистрироваться или войти в систему, используя свой адрес электронной почты и пароль.

После того как пользователь успешно войдет в систему, вашему приложению потребуется сохранить его основные данные, в первую очередь уникальный идентификатор пользователя и выбранное им имя пользователя.

Включить аутентификацию Firebase

В консоли Firebase вашего проекта перейдите в раздел «Аутентификация» и включите аутентификацию Firebase. Затем включите поставщика аутентификации по электронной почте и паролю.

В локальной папке проекта найдите firebase.json и обновите его следующим образом, чтобы включить эмулятор аутентификации Firebase.

{
  "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 {
    ...
  }
}

Этот код использует upsertUserMutation Firebase Data Connect, сгенерированный для вставки нового пользователя (или обновления существующего пользователя с тем же идентификатором) всякий раз, когда пользователь успешно регистрируется с использованием аутентификации Firebase.

Посмотрите на это в действии

Чтобы проверить, работает ли это, сначала зарегистрируйтесь в приложении для iOS:

  • Если вы этого не сделали, остановите и перезапустите эмулятор Firebase, чтобы убедиться, что эмулятор аутентификации Firebase запущен.
  • В Xcode нажмите кнопку «Запустить» , чтобы запустить приложение в iOS Simulator.
  • Нажмите на значок аватара в правом верхнем углу экрана.
  • Перейдите на страницу регистрации и зарегистрируйтесь в приложении.

Затем выполните запрос к базе данных, чтобы убедиться, что приложение создало новую учетную запись пользователя:

  • В VS Code откройте dataconnect/schema/schema.gql и нажмите Read data для сущности User
  • Это создаст новый файл запроса с именем User_read.gql
  • Нажмите « Запустить локально», чтобы увидеть всех пользователей в таблице пользователей.
  • На панели «Выполнение подключения к данным» вы теперь должны увидеть учетную запись пользователя, с которым вы только что зарегистрировались.

9. Управление любимыми фильмами

В этом разделе лабораторной работы вы реализуете взаимодействие с пользователем в приложении для просмотра фильмов, в частности, позволите пользователям управлять своими любимыми фильмами. Фильмы, добавленные в избранное, будут отображаться в списке просмотра приложения.

Улучшить схему для поддержки избранного

Тип FavoriteMovie — это таблица, которая поддерживает связи типа «многие ко многим» между пользователями и их любимыми фильмами. Каждая таблица связывает User с Movie .

Скопируйте и вставьте фрагмент кода в файл dataconnect/schema/schema.gql :

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

Определить мутации для добавления и удаления избранных

Прежде чем приложение сможет отображать любимые фильмы пользователя, ему необходимо указать, какие из них являются его любимыми. Для этого сначала нужно добавить две мутации: чтобы добавить фильм в список любимых или удалить его из списка избранного, соответственно.

  1. В VS Code откройте mutations.gql в dataconnect/connector/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 })
}

Подключите мутации к пользовательскому интерфейсу вашего приложения

Пользователи могут добавить фильм в избранное, нажав на значок сердца на экране сведений о фильме.

Чтобы подключить только что созданные мутации к пользовательскому интерфейсу приложения, внесите следующие изменения в 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)
      }
    }
  }
}

Это обновит статус избранного текущего фильма в базе данных. Последний шаг, которого не хватает, — это обеспечение соответствующего отображения состояния пользовательского интерфейса.

Определите запрос, чтобы выяснить, отмечен ли фильм как избранный.

  1. В VS Code откройте queries.gql в dataconnect/connector .
  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 откройте queries.gql в dataconnect/connector/queries.gql и вставьте следующий запрос:
## Get favorite movies by user ID
query GetUserFavoriteMovies @auth(level: USER) {
  user(id_expr: "auth.uid") {
    favoriteMovies: favorite_movies_on_user {
      movie {
        id
        title
        genre
        imageUrl
        releaseYear
        rating
        description
      }
    }
  }
}

Список любимых фильмов пользователя отображается на LibraryScreen . Этот экран должен отображать данные только в том случае, если пользователь вошел в систему, поэтому сначала необходимо подключить состояние аутентификации экрана к AuthenticationService приложения.

  1. Добавьте код для сопоставления FavoriteMovieFavoriteMovies с Movie в Movie+DataConnect.swift :
import FirebaseDataConnect
import FriendlyFlixSDK

extension Movie {

  ...

  init(from: GetUserFavoriteMoviesQuery.Data.User.FavoriteMovieFavoriteMovies) {
    id = from.movie.id
    title = from.movie.title
    description = from.movie.description ?? ""
    releaseYear = from.movie.releaseYear
    rating = from.movie.rating
    imageUrl = from.movie.imageUrl
  }
}
  1. В Xcode откройте LibraryScreen , затем обновите isSignedIn следующим образом:
struct LibraryScreen: View {
  ...

  private var isSignedIn: Bool {
    authenticationService.user != nil
  }

}
  1. Затем импортируйте Firebase Data Connect и FriendlyFlixSDK и получите ссылку на запрос GetUserFavoriteMovies :
import SwiftUI
import FirebaseDataConnect
import FriendlyFlixSDK

struct LibraryScreen {

 ...

  private var connector = DataConnect.friendlyFlixConnector

  ...

  init() {
    watchListRef = connector.getUserFavoriteMoviesQuery.ref()
  }

  private let watchListRef: QueryRefObservation<
    GetUserFavoriteMoviesQuery.Data,
    GetUserFavoriteMoviesQuery.Variables
  >
  private var watchList: [Movie] {
    watchListRef.data?.user?.favoriteMovies.map(Movie.init) ?? []
  }

  ...

}


  1. Убедитесь, что запрос watchListRef выполняется при появлении представления:
extension LibraryScreen: View {
  var body: some View {
    ...
            MovieListSection(namespace: namespace, title: "Watch List", movies: watchList)
              .onAppear {
                Task {
                  try await watchListRef.execute()
                }
  ...

Посмотрите на это в действии

Теперь вы можете запустить приложение и опробовать только что добавленную функцию «Избранное». Обратите внимание на несколько моментов:

  • Убедитесь, что эмулятор Firebase запущен.
  • Убедитесь, что вы добавили фиктивные данные для фильмов и информацию о них.
  • Убедитесь, что вы зарегистрировались как пользователь.
  1. В Xcode нажмите кнопку «Запустить» , чтобы запустить приложение в iOS Simulator.
  2. После запуска приложения нажмите на карточку фильма, чтобы отобразить подробную информацию о нем.
  3. Нажмите на значок сердца, чтобы добавить фильм в избранное. Сердце должно стать сплошным.
  4. Повторите это для нескольких фильмов.
  5. Перейдите на вкладку «Библиотека». Теперь вы увидите список всех фильмов, которые вы добавили в избранное.

10. Поздравления

Поздравляем! Вы успешно добавили Firebase Data Connect в приложение iOS! Теперь вы знаете основные шаги, необходимые для настройки Data Connect, создания запросов и мутаций, а также для аутентификации пользователей.

Необязательно: развернуть в производстве

До сих пор это приложение использовало только эмуляторы Firebase. Если вы хотите узнать, как развернуть это приложение в реальном проекте Firebase, перейдите к следующему шагу.

11. (Необязательно) Разверните свое приложение.

До сих пор это приложение было полностью локальным, все данные хранились в Firebase Emulator Suite. В этом разделе вы узнаете, как настроить проект Firebase для работы этого приложения в продакшене.

Включить аутентификацию Firebase

  1. В консоли Firebase перейдите в раздел Аутентификация и нажмите Начать .
  2. Перейдите на вкладку «Способ входа» .
  3. Выберите опцию «Электронная почта/Пароль» в разделе собственных поставщиков.
  4. Включите поставщика электронной почты/пароля, затем нажмите « Сохранить» .

Включить Firebase Data Connect

Важно: Если вы впервые развёртываете схему в своём проекте, в ходе этого процесса будет создан экземпляр Cloud SQL PostgreSQL, что может занять около 15 минут. Вы не сможете выполнить развёртывание, пока экземпляр Cloud SQL не будет готов и интегрирован с Firebase Data Connect.

1. В интерфейсе расширения Firebase Data Connect VS Code нажмите кнопку «Развернуть в рабочей среде» . 2. Возможно, потребуется проверить изменения схемы и одобрить потенциально деструктивные модификации. Вам будет предложено: — проверить изменения схемы с помощью firebase dataconnect:sql:diff ; — если вы удовлетворены изменениями, примените их с помощью потока, запущенного командой firebase dataconnect:sql:migrate

Ваш экземпляр Cloud SQL для PostgreSQL будет обновлён с учётом окончательной развёрнутой схемы и данных. Вы можете отслеживать статус обновления в консоли Firebase.

Теперь вы можете нажать кнопку «Запустить (Производство)» на панели Firebase Data Connect, как вы это делали с локальными эмуляторами, чтобы добавить данные в производственную среду.

Перед повторным запуском приложения iOS убедитесь, что оно подключено к производственному экземпляру вашего проекта:

  1. Откройте меню Продукт > Схема > Редактировать схему....
  2. В разделе «Выполнить» снимите флажок с аргумента запуска -useEmulator YES .