Сборка с помощью 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 Emulator для быстрой обработки данных.
  • Разработайте схему базы данных , используя Data Connect и GraphQL .
  • Создайте типобезопасный Swift SDK на основе схемы вашей базы данных и добавьте его в Swift-приложение.
  • Внедрите аутентификацию пользователей и интегрируйте ее с Firebase Data Connect для защиты данных ваших пользователей.
  • Получайте, обновляйте, удаляйте и управляйте данными в Cloud SQL с помощью запросов и мутаций, основанных на GraphQL.
  • (При желании) Разверните службу Data Connect в рабочей среде.

Предварительные требования

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

2. Настройте тестовый проект.

Создайте проект Firebase.

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

Скачать код

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

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

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

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

SDK Firebase использует конфигурационный файл для подключения к вашему проекту 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 .

Инициализируйте подключение к данным в проекте.

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

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

Настройка генерации SDK

После нажатия кнопки « Запустить инициализацию Firebase » расширение 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 — путь, куда будет сохранен сгенерированный SDK Data Connect. Этот путь является относительным к каталогу, содержащему файл 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.

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.

Свяжите запрос 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.

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

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

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. Проверьте терминал выполнения подключения данных, чтобы убедиться в успешном добавлении данных.

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

Определите запрос 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. Внедрить аутентификацию пользователей.

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

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

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

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

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

В консоли Firebase для вашего проекта перейдите в раздел «Аутентификация» и включите аутентификацию 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 Data Connect объект upsertUserMutation для добавления нового пользователя (или обновления существующего пользователя с тем же ID) всякий раз, когда пользователь успешно регистрируется с помощью аутентификации Firebase.

Посмотрите, как это работает.

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

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

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

  • В VS Code откройте dataconnect/schema/schema.gql и нажмите «Чтение данных» для сущности 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 to 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

  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 for PostgreSQL будет обновлен с использованием окончательно развернутой схемы и данных. Вы можете отслеживать статус в консоли Firebase.

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

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

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