Включение автономных возможностей

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

Постоянство диска

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

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

Вы можете включить сохранение диска с помощью всего лишь одной строки кода.

FirebaseDatabase.instance.setPersistenceEnabled(true);

Поведение настойчивости

Включив сохранение, любые данные, которые клиент базы данных Firebase Realtime будет синхронизировать в режиме онлайн, сохраняются на диске и доступны в автономном режиме, даже когда пользователь или операционная система перезапускает приложение. Это означает, что ваше приложение работает так же, как и в Интернете, используя локальные данные, хранящиеся в кеше. Обратные вызовы прослушивателя будут продолжать срабатывать для локальных обновлений.

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

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

Сохранение свежести данных

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

final scoresRef = FirebaseDatabase.instance.ref("scores");
scoresRef.keepSynced(true);

Клиент базы данных Firebase Realtime автоматически загружает данные в эти места и синхронизирует их, даже если у ссылки нет активных прослушивателей. Вы можете отключить синхронизацию с помощью следующей строки кода.

scoresRef.keepSynced(false);

По умолчанию кэшируется 10 МБ ранее синхронизированных данных. Этого должно быть достаточно для большинства приложений. Если кеш превышает настроенный размер, база данных Firebase Realtime удаляет данные, которые использовались последними. Данные, которые синхронизируются, не удаляются из кэша.

Запрос данных в автономном режиме

База данных Firebase Realtime хранит данные, полученные из запроса, для использования в автономном режиме. Для запросов, созданных в автономном режиме, база данных Firebase Realtime продолжает работать с ранее загруженными данными. Если запрошенные данные не загружены, база данных Firebase Realtime загружает данные из локального кеша. Когда сетевое подключение снова станет доступным, данные загрузятся и отразят запрос.

Например, этот код запрашивает последние четыре элемента в базе данных оценок:

final scoresRef = FirebaseDatabase.instance.ref("scores");
scoresRef.orderByValue().limitToLast(4).onChildAdded.listen((event) {
  debugPrint("The ${event.snapshot.key} dinosaur's score is ${event.snapshot.value}.");
});

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

scoresRef.orderByValue().limitToLast(2).onChildAdded.listen((event) {
  debugPrint("The ${event.snapshot.key} dinosaur's score is ${event.snapshot.value}.");
});

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

Если бы приложение запросило последние шесть элементов в автономном режиме, оно сразу же получило бы события «дочерние добавленные» для четырех кэшированных элементов. Когда устройство снова подключается к сети, клиент базы данных Firebase Realtime синхронизируется с сервером и получает последние два события «добавлен дочерний элемент» и «значение» для приложения.

Обработка транзакций в автономном режиме

Любые транзакции, выполняемые, когда приложение находится в автономном режиме, ставятся в очередь. Как только приложение восстанавливает подключение к сети, транзакции отправляются на сервер базы данных реального времени.

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

Управление присутствием

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

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

Вот простой пример записи данных при отключении с помощью примитива onDisconnect :

final presenceRef = FirebaseDatabase.instance.ref("disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnect().set("I disconnected!");

Как работает onDisconnect

Когда вы устанавливаете операцию onDisconnect() , она выполняется на сервере базы данных Firebase Realtime. Сервер проверяет безопасность, чтобы убедиться, что пользователь может выполнить запрошенное событие записи, и сообщает вашему приложению, если оно недействительно. Затем сервер контролирует соединение. Если в какой-то момент время соединения истекает или оно активно закрывается клиентом базы данных реального времени, сервер проверяет безопасность второй раз (чтобы убедиться, что операция все еще действительна), а затем вызывает событие.

try {
    await presenceRef.onDisconnect().remove();
} catch (error) {
    debugPrint("Could not establish onDisconnect event: $error");
}

Событие onDisconnect также можно отменить, вызвав .cancel() :

final onDisconnectRef = presenceRef.onDisconnect();
onDisconnectRef.set("I disconnected");
// ...
// some time later when we change our minds
// ...
onDisconnectRef.cancel();

Определение состояния соединения

Для многих функций, связанных с присутствием, вашему приложению полезно знать, когда оно онлайн или оффлайн. База данных Firebase Realtime предоставляет специальное местоположение в /.info/connected , которое обновляется каждый раз, когда изменяется состояние подключения клиента базы данных Firebase Realtime. Вот пример:

final connectedRef = FirebaseDatabase.instance.ref(".info/connected");
connectedRef.onValue.listen((event) {
  final connected = event.snapshot.value as bool? ?? false;
  if (connected) {
    debugPrint("Connected.");
  } else {
    debugPrint("Not connected.");
  }
});

/.info/connected — это логическое значение, которое не синхронизируется между клиентами базы данных реального времени, поскольку это значение зависит от состояния клиента. Другими словами, если один клиент читает /.info/connected как ложь, это не гарантирует, что другой клиент также прочтет ложь.

Обработка задержки

Временные метки сервера

Серверы базы данных Firebase Realtime предоставляют механизм для вставки временных меток, сгенерированных на сервере, в качестве данных. Эта функция в сочетании с onDisconnect обеспечивает простой способ надежного отслеживания времени отключения клиента базы данных реального времени:

final userLastOnlineRef =
    FirebaseDatabase.instance.ref("users/joe/lastOnline");
userLastOnlineRef.onDisconnect().set(ServerValue.timestamp);

Перекос часов

Хотя ServerValue.timestamp гораздо более точен и предпочтителен для большинства операций чтения/записи, иногда он может быть полезен для оценки отклонения часов клиента по отношению к серверам базы данных Firebase Realtime. Вы можете прикрепить обратный вызов к местоположению /.info/serverTimeOffset чтобы получить значение в миллисекундах, которое клиенты базы данных Firebase Realtime добавляют к локальному отчетному времени (время эпохи в миллисекундах) для оценки времени сервера. Обратите внимание, что на точность этого смещения может влиять задержка в сети, поэтому оно полезно в первую очередь для обнаружения больших (> 1 секунды) расхождений во времени.

final offsetRef = FirebaseDatabase.instance.ref(".info/serverTimeOffset");
offsetRef.onValue.listen((event) {
  final offset = event.snapshot.value as num? ?? 0.0;
  final estimatedServerTimeMs =
      DateTime.now().millisecondsSinceEpoch + offset;
});

Пример приложения присутствия

Комбинируя операции отключения с мониторингом состояния соединения и временными метками сервера, вы можете построить систему присутствия пользователей. В этой системе каждый пользователь хранит данные в определенном месте базы данных, чтобы указать, находится ли клиент базы данных реального времени в сети. Клиенты устанавливают для этого местоположения значение true, когда они подключаются к сети, и метку времени, когда они отключаются. Эта временная метка указывает, когда данный пользователь в последний раз был онлайн.

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

// Since I can connect from multiple devices, we store each connection
// instance separately any time that connectionsRef's value is null (i.e.
// has no children) I am offline.
final myConnectionsRef =
    FirebaseDatabase.instance.ref("users/joe/connections");

// Stores the timestamp of my last disconnect (the last time I was seen online)
final lastOnlineRef =
    FirebaseDatabase.instance.ref("/users/joe/lastOnline");

final connectedRef = FirebaseDatabase.instance.ref(".info/connected");
connectedRef.onValue.listen((event) {
  final connected = event.snapshot.value as bool? ?? false;
  if (connected) {
    final con = myConnectionsRef.push();

    // When this device disconnects, remove it.
    con.onDisconnect().remove();

    // When I disconnect, update the last time I was seen online.
    lastOnlineRef.onDisconnect().set(ServerValue.timestamp);

    // Add this device to my connections list.
    // This value could contain info about the device or a timestamp too.
    con.set(true);
  }
});