# Dart (Flutter)

**URL:** https://heroiclabs.com/docs/satori/client-libraries/dart/
**Summary:** The official Dart client handles all communication with the Satori server, enabling management of your live game via sending analytics events, updating properties, getting feature flags and experiments, and more.
**Keywords:** dart client guide, flutter client, satori Dart, authentication, user accounts, audiences, experiments, live events, feature flags, identities, liveops Dart
**Categories:** satori, dart, client-libraries

---


# Satori Dart Client Guide

This client library guide will show you how to use the core Satori features in **Dart**.

## Prerequisites

Before proceeding ensure that you have:

* Access to Satori server instance
* Installed the [Satori Dart SDK](#installation)

{{< note "important" >}}
The Satori client is packaged as part of the Nakama Dart SDK, but using Nakama **is not** required.
{{< / note >}}

### Full API documentation

For the full API documentation please visit the [API docs](https://pub.dev/documentation/satori/latest/).

### Installation

The client is available on:

* [pub.dev](https://pub.dev/packages/satori)
* [GitHub Releases page](https://github.com/heroiclabs/nakama-dart/releases)

To install the package in your project run:

```bash
dart pub add satori
```

#### Updates

New versions of the Satori Dart Client and the corresponding improvements are documented in the [Changelog](https://github.com/heroiclabs/nakama-dart/blob/main/satori/CHANGELOG.md).

## Getting started

Learn how to get started using the Satori Client to manage your live game.

### Satori client

The Satori Client connects to a Satori Server and is the entry point to access Satori features. It is recommended to have one client per server per game.

To create a client pass in your server connection details:

```dart
final client = getSatoriClient(
    host: '127.0.0.1',
    port: 7450,
    ssl: false,
    apiKey: 'apikey',
);
```

<!-- ### Configuring the request timeout length

Each request to Satori from the client must complete in a certain period of time before it is considered to have timed out. You can configure how long this period is (in seconds) by setting the `timeout` value in the client:

```dart
client.timeout = 10
```
-->

{{< note "important" "API Keys">}}
Create and rotate API keys in the [Satori Settings page](../../concepts/settings/#api-keys).
{{< / note >}}

## Authentication

Authenticate users using the Satori Client via their unique ID.

When authenticating, you can optionally pass in any desired `defaultProperties` and/or `customProperties` to be updated. If none are provided, the properties remain as they are on the server.

```dart
Future<void> authenticateWithDevice() async {
    final String deviceId = '<your-device-id>'; // Retrieve device id from device_info_plus package for iOS & Android or similar

    final defaultProperties = {
        'platform': Platform.operatingSystem, // from dart:io
        'language': Platform.localeName,
        'client_version': '1.0.0', // Replace with your app version
        'version': '1.0.0',
    };

    try {
        // Authenticate with the Satori server
        final session = await client.authenticate(id: deviceId, defaultProperties: defaultProperties);
    } catch(e) {
        print('Authentication failed');
    }
}
```

When authenticated the server responds with an auth token (JWT) which contains useful properties and gets deserialized into a `Session` object.


### Session lifecycle

Sessions expire after five (5) minutes by default. Expiring inactive sessions is good security practice.

Satori provides ways to restore sessions, for example when players re-launch the game, or refresh tokens to keep the session active while the game is being played.

Use the auth and refresh tokens on the session object to restore or refresh sessions.

Store the tokens in `shared_prefs` or preferably more secure storage.

To restore a session without having to re-authenticate just pass the session which is about to expire to `sessionRefresh` method and you'll receive a new one:

```dart
try {
    final session = await client.sessionRefresh(session: expiringSession);
} catch(e) {
    print('Error refreshing the session');
}
```

<!-- #### Automatic session refresh

The Dart client library includes a feature where sessions close to expiration are automatically refreshed.

This is enabled by default but can be configured when first creating the [Satori client](#satori-client) using the following parameters:

* `autoRefresh` - Boolean value indicating if this feature is enabled, `true` by default

Check if a session has expired or is close to expiring and refresh it to keep it alive:
-->

#### Manually refreshing a session

Sessions can be manually refreshed.

```dart
session = await client.sessionRefresh(session: session);
```

### Ending sessions

Logout and end the current session:

```dart
await client.authenticateLogout(session: session);
```

## Experiments

Satori [Experiments](../../concepts/experiments/) allow you to test different game features and configurations in a live game.

List the current experiments for this user:

```dart
await client.getAllExperiments(session: session);
```

You can also specify an array of experiment names you wish to get:

```dart
var experiments = await client.getExperiments(
  session: session,
  names: [
    'ExperimentOne',
    'ExperimentTwo',
  ],
);
```

## Feature flags

Satori [feature flags](../../concepts/remote-configuration/) allow you to enable or disable features in a live game.

### Get a single flag

Get a single feature flag for this user:

```dart
var flag = await client.getFlag(
  session: session,
  name: 'FlagName',
);
```

You can also specify a default value for the flag if a value cannot be found:

```dart
var flag = await client.getFlag(
  session: session,
  name: 'FlagName',
  defaultValue: 'DefaultValue',
);
```

Specifying a default value ensures no exception will be thrown if the network is unavailable, instead a flag with the specified default value will be returned.

### Get a single default flag

Get a single default flag for this user:

```dart
var flag = await client.getFlagDefault(
  session: session,
  name: 'FlagName',
);
```

Similar to the `getFlag` method, you can also provide a default value for default flags:

```dart
var flag = await client.getFlagDefault(
  session: session,
  name: 'FlagName',
  defaultValue: 'DefaultValue',
);
```

Specifying a default value ensures no exception will be thrown if the network is unavailable, instead a flag with the specified default value will be returned.

### Listing identity flags

List the available feature flags for this user:

```dart
var flags = await client.getFlags(
  session: session,
  names: [],
);
```

### Listing default flags

List all default feature flags:

```dart
var flags = await client.getFlagsDefault(session: session);
```

## Events

Satori [Events](../../concepts/performance-monitoring/understand-events/) allow you to send data for a given user to the server for processing.

{{< note "important" "Metadata Limits">}}
The maximum size of the `metadata` field is `4096` bytes.
{{< / note >}}

### Sending single events

```dart
await client.event(
  session: session,
  event: Event(
    name: 'gameFinished',
    timestamp: DateTime.now(),
  ),
);
```

### Sending multiple events

```dart
await client.events(
  session: session,
  events: [
    Event(name: 'adStarted', timestamp: DateTime.now()), 
    Event(name: 'appLaunched', timestamp: DateTime.now()),
  ],
);
```

## Live events

Satori [Live Events](../../concepts/live-events/) allow you to deliver established features to your players on a custom schedule.

### List all available live events:

```dart
var liveEvents = await client.getLiveEvents(session: session);
```

### Join explicit live events

```dart
await client.joinLiveEventAsync(session, liveEventID);
```

## Identities

Satori [Identities](/docs/satori/concepts/segmentation/) identify individual players of your game and can be enriched with custom properties.

### List an identity's properties

```dart
var properties = await client.listProperties(session: session);
```

### Update an identity's properties

```dart
var defaultProperties = {
  'DefaultPropertyKey': 'DefaultPropertyValue',
  'AnotherDefaultPropertyKey': 'AnotherDefaultPropertyValue',
};

var customProperties = {
	'CustomPropertyKey': 'CustomPropertyValue',
	'AnotherCustomPropertyKey': 'AnotherCustomPropertyValue',
};

await client.updateProperties(
  session: session,
  defaultProperties: defaultProperties,
  customProperties: customProperties,
);
```

You can immediately re-evaluate the user's audience memberships upon updating their properties by passing `recompute` as `true`:

```dart
var recompute = true;

await client.updateProperties(
  session: session,
  defaultProperties: defaultProperties,
  customProperties: customProperties,
  recompute: recompute,
);
```

### Identifying a session with a new ID

If you want to submit events to Satori before a user has authenticated with the game backend (e.g. Nakama) and has a User ID, you should authenticate with Satori using a temporary ID, such as a randomly generated one.

```dart
var tempId = '<temp_id>';

// Authenticate with Satori
var session = client.authenticate(tempId);
```

You can then submit events before the user has authenticated with the game backend.

```dart
await client.event(
  session: session,
  Event(
    name: 'gameFinished',
    timestamp: DateTime.now(),
  ),
);
```

The user would then authenticate with the game backend and retrieve their User ID from the session:

```dart
var nakamaSession = await nakamaClient.authenticateCustom('<nakamaCustomId>');
var nakamaUserId = nakamaSession.userId;
```

Once a user has successfully authenticated, you should then call `identify` to enrich the current session and return a new session that should be used for submitting future events.

```dart
var newSession = await client.identify(
  session: session,
  id: nakamaUserId,
  defaultProperties: {},
  customProperties: {},
);
```

Note that the old session is no longer valid and cannot be used after this.

### Deleting an identity

Delete the calling identity and its associated data:

```dart
await client.deleteIdentity(session: session);
```
