Firebase SQL Connect client SDKs let you call your server-side queries and
mutations directly from a Firebase app. You generate a custom client SDK in
parallel as you design the schemas, queries and mutations you deploy to your
SQL Connect service. Then, you integrate methods from this SDK into
your client logic.

As we've mentioned elsewhere, it's important to note that SQL Connect
queries and mutations are not submitted by client code and executed on the
server. Instead, when deployed, SQL Connect operations are stored on
the server like Cloud Functions. This means you need to deploy corresponding
client-side changes to avoid breaking existing users (for example, on older app
versions).

That's why SQL Connect provides you with a developer environment and
tooling that lets you prototype your server-deployed schemas, queries and mutations.
It also generates client-side SDKs automatically, while you prototype.

When you've iterated updates to your service and client apps, both server- and
client-side updates are ready to deploy.

## What is the client development workflow?

If you followed the [Get started](https://firebase.google.com/docs/sql-connect/quickstart.md.txt), you were
introduced to the overall development flow for SQL Connect. In this
guide, you'll find more detailed information about generating Swift SDKs from
your schema and working with client queries and mutations.

> [!NOTE]
> You can learn client development with a quickstart app repository and build a fully-featured SQL Connect app by following our [codelab for iOS](https://firebase.google.com/codelabs/firebase-dataconnect-ios).

To summarize, to use generated Swift SDKs in your client apps, you'll follow
these prerequisite steps:

1. Add Firebase to your [iOS](https://firebase.google.com/docs/ios/setup.md.txt) app.
2. To use the generated SDK, configure it as a dependency in Xcode.

   In the Xcode top navigation bar, select
   **File \> Add Package Dependencies \> Add Local** , and choose the folder
   containing the generated `Package.swift`.

Then:

1. Develop your app schema.
2. Set up SDK generation:

   - With the **Add SDK to app** button in our SQL Connect VS Code extension
   - By [updating your `connector.yaml`](https://firebase.google.com/docs/sql-connect/ios-sdk.md.txt#generate-swift)
3. [Initialize your client code and import libraries](https://firebase.google.com/docs/sql-connect/ios-sdk.md.txt#initialize-sdk).

4. [Implement calls to queries and mutations](https://firebase.google.com/docs/sql-connect/ios-sdk.md.txt#queries-mutations).

5. [Set up and use the SQL Connect emulator](https://firebase.google.com/docs/sql-connect/ios-sdk.md.txt#prototype-and) and
   iterate.

## Generate your Swift SDK

Use the Firebase CLI to set up SQL Connect generated SDKs in your apps.
The `init` command should detect all apps in the current folder and install
generated SDKs automatically.

```sql
firebase init dataconnect:sdk
```

### Update SDKs while prototyping

If you have SQL Connect VS Code extension installed, it will always keep generated
SDKs up to date.

If you don't use SQL Connect VS Code extension, you can use Firebase CLI
to keep generated SDKs up to date.

```bash
firebase dataconnect:sdk:generate --watch
```

### Generate SDKs in build pipelines

You can use the Firebase CLI to generate SQL Connect SDKs in CI/CD build processes.

```bash
firebase dataconnect:sdk:generate
```

## Initialize the SQL Connect iOS SDK

Initialize your SQL Connect instance using the information you
used to set up SQL Connect. Find this information in the
**Databases \& Storage** \>
[**SQL Connect** page](https://console.firebase.google.com/project/_/dataconnect)
of the Firebase console.

### Get a connector instance

The code for your connector will be generated by the
SQL Connect emulator. If your connector name is `movies` and the
package is `movies`, as specified in `connector.yaml`, then
retrieve the connector object by calling:

```swift
let connector = DataConnect.moviesConnector
```

## Implement queries and mutations

With the connector object, you can run queries and mutations as defined in the
GraphQL source code. Suppose your connector has these operations defined:

```graphql
mutation createMovie($title: String!, $releaseYear: Int!, $genre: String!, $rating: Int!) {
  movie_insert(data: {
    title: $title
    releaseYear: $releaseYear
    genre: $genre
    rating: $rating
  })
}

query getMovieByKey($key: Movie_Key!) {
  movie(key: $key) { id title }
}

query listMoviesByGenre($genre: String!) {
  movies(where: {genre: {eq: $genre}}) {
    id
    title
  }
}
```

You can then create a movie as follows:

```swift
let mutationResult = try await connector.createMovieMutation.execute(
  title: "Empire Strikes Back",
  releaseYear: 1980,
  genre: "Sci-Fi",
  rating: 5)

print("Movie ID: \(mutationResult.data.movie_insert.id)")
```

To retrieve a movie, you will use a query reference. All query refs are
Observable publishers. Depending on the configured publisher (see `connector.yaml)`,
they either support the `@Observable` macro (iOS 17+) or implement the
`ObservableObject` protocol. The default, if none is specified, is the
`@Observable` macro supported on iOS 17+.

In a SwiftUI view, you can bind the query results using the published `data`
variable of the query ref and call the query's `execute()` method to update
the data. The `data` variable will match the shape of data that was defined
in your GQL query definition.

All retrieved results comply with the `Decodable` protocol. If you included
the object's primary key in your GQL fetch, the objects are also `Identifiable`,
allowing you to use them in iterators.

> [!NOTE]
> **Note:** The `@StateObject` is not updated in realtime. It is also not necessary when using an `@Observable` macro as the publisher.

```swift
struct ListMovieView: View {
    @StateObject private var queryRef = connector.listMoviesByGenreQuery.ref(genre: "Sci-Fi")
    var body: some View {
        VStack {
            Button {
                Task {
                    do {
                        try await refresh()
                    } catch {
                        print("Failed to refresh: \(error)")
                    }
                }
            } label: {
                Text("Refresh")
            }
                // use the query results in a view
            ForEach(queryRef.data?.movies ?? [], id: \.self.id) { movie in
                    Text(movie.title)
                }
            }
    }
    @MainActor
    func refresh() async throws {
        _ = try await queryRef.execute()
    }
}
```

Queries also support one-shot execution.

```swift
let resultData = try await DataConnect.moviesConnector.listMoviesByGenreQuery.execute(genre: "Sci-Fi")
```

### Subscribe to changes

See [Get real-time updates from SQL Connect](https://firebase.google.com/docs/sql-connect/realtime.md.txt).

### Handle changes to enumeration fields

An app's schema can [contain enumerations](https://firebase.google.com/docs/sql-connect/schemas-guide.md.txt#enumeration-fields),
which can be accessed by your [GraphQL queries](https://firebase.google.com/docs/sql-connect/queries-guide.md.txt#query-enumerations).

As an app's design changes, you may add new enum supported values. For example,
imagine that later in your application's lifecycle you decide to add a
FULLSCREEN value to the `AspectRatio` enum.

In the SQL Connect workflow, you can use local development tooling to
update your queries and SDKs.

However, before you release an updated version of your clients, older deployed
clients may break.

#### Example resilient implementation

The generated SDK forces handling of unknown values as the generated enums
contain an `_UNKNOWN` value, and Swift enforces exhaustive switch statements.

```swift
do {
    let result = try await DataConnect.moviesConnector.listMovies.execute()
    if let data = result.data {
        for movie in data.movies {
            switch movie.aspectratio {
                case .ACADEMY: print("academy")
                case .WIDESCREEN: print("widescreen")
                case .ANAMORPHIC: print("anamorphic")
                case ._UNKNOWN(let unknownAspect): print(unknownAspect)
            }
        }
    }
} catch {
    // handle error
}
```

## Enable client-side caching

SQL Connect has an optional client-side caching feature, which you
can enable by editing the `connector.yaml` file. When this feature is enabled,
the generated client SDKs will locally cache query responses, which can reduce
the number of database requests your app makes and enables the
database-dependent portions of your app to work when network availability is
interrupted.

To enable client-side caching, add a client caching configuration to your
connector configuration:


```yaml
generate:
  swiftSdk:
    outputDir: "../ios"
    package: "FirebaseDataConnectGenerated"
    clientCache:
maxAge: 5s
storage: persistent
```

<br />

This configuration has two parameters, both optional:

- `maxAge`: The maximum age a cached response can be before the client SDK
  fetches fresh values. Examples: "0", "30s", "1h30m".

  The default value for `maxAge` is `0`, which means that responses are cached,
  but the client SDK will always fetch fresh values. The cached values will only
  be used when `CACHE_ONLY` is specified to `execute()`.
- `storage`: The client SDK can be configured to cache responses either in
  `persistent` storage or in `memory`. Results cached in `persistent` storage
  will persist across app restarts. In iOS SDKs, the default is `persistent`.

After you update your connector's caching configuration, regenerate your client
SDKs and rebuild your app. Once you have done so, `execute()`
will cache
responses and use cached values according to the policy you configured. This
generally happens automatically, without any additional steps on your part;
however, note the following:

- The default behavior of `execute()` is as described above: if a
  result is cached for a query and the cached value is not older than `maxAge`,
  then use the cached value. This default behavior is called the `PREFER_CACHE`
  policy.

  You can also specify to individual invocations of `execute()` to
  either only serve cached values (`CACHE_ONLY`) or to unconditionally fetch
  fresh values from the server (`SERVER_ONLY`).


  ```swift
  try await execute(fetchPolicy: .cacheOnly)
  ```

  ```swift
  try await execute(fetchPolicy: .serverOnly)
  ```

  ## Prototype and test your iOS application

  You can use the local emulator to test and prototype your application.

  ### Instrument clients to use a local emulator

  You can use the SQL Connect emulator, whether from the
  SQL Connect VS Code extension or from the CLI.

  Instrumenting the app to connect to the emulator is the same for both scenarios.

  ```swift
  let connector = DataConnect.moviesConnector
  // Connect to the emulator on "127.0.0.1:9399"
  connector.useEmulator()

  // (alternatively) if you're running your emulator on non-default port:
  connector.useEmulator(port: 9999)

  // Make calls from your app
  ```

  ## Data types in SQL Connect SDKs

  The SQL Connect server represents common and custom GraphQL data
  types. These are represented in the SDK as follows.

  | SQL Connect Type | Swift |
  |---|---|
  | String | String |
  | Int | Int |
  | Float | Double |
  | Boolean | Bool |
  | UUID | UUID |
  | Date | FirebaseDataConnect.LocalDate |
  | Timestamp | FirebaseCore.Timestamp |
  | Int64 | Int64 |
  | Any | FirebaseDataConnect.AnyValue |