Firebase 애플리케이션은 일시적으로 네트워크 연결이 끊겨도 정상적으로 작동합니다. 또한 Firebase는 로컬에서 데이터를 유지하고, 접속 상태를 관리하고, 지연 시간을 처리하는 도구를 제공합니다.
디스크 지속성
Firebase 앱은 일시적인 네트워크 중단을 자동으로 처리합니다. 오프라인 상태에서는 캐시된 데이터를 사용할 수 있고, 네트워크 연결이 복원되면 Firebase에서 모든 쓰기 작업을 다시 전송합니다.
디스크 지속성을 사용 설정하면 앱의 데이터를 기기에 로컬로 저장하므로 오프라인 상태일 때도 앱이 현재 상태를 유지할 수 있으며, 사용자 또는 운영체제가 앱을 다시 시작하더라도 유지됩니다.
단 한 줄의 코드로 디스크 지속성을 사용 설정할 수 있습니다.
FirebaseDatabase.instance.setPersistenceEnabled(true);
지속성 동작
지속성을 사용 설정하면 Firebase 실시간 데이터베이스 클라이언트가 온라인 상태에서 동기화하는 모든 데이터가 디스크에 유지되고 오프라인 상태에서 사용 가능해지며, 사용자 또는 운영체제가 앱을 다시 시작하더라도 마찬가지입니다. 따라서 캐시에 저장된 로컬 데이터를 사용하여 온라인일 때와 다름없이 앱이 작동합니다. 로컬 업데이트 시 리스너 콜백도 계속 발생합니다.
Firebase 실시간 데이터베이스 클라이언트는 앱이 오프라인일 때 수행된 모든 쓰기 작업을 자동으로 큐에 유지합니다. 지속성을 사용 설정하면 이 큐가 디스크에도 유지되므로 사용자 또는 운영체제가 앱을 다시 시작해도 쓰기 작업이 사라지지 않습니다. 앱이 다시 연결되면 모든 작업이 Firebase 실시간 데이터베이스 서버로 전송됩니다.
앱이 Firebase 인증을 사용하는 경우 앱을 다시 시작해도 Firebase 실시간 데이터베이스 클라이언트에서 사용자의 인증 토큰을 유지합니다. 앱이 오프라인일 때 인증 토큰이 만료되면 앱에서 사용자를 다시 인증할 때까지 클라이언트가 쓰기 작업을 일시중지하며, 인증되지 않으면 보안 규칙으로 인해 쓰기 작업에 실패할 수 있습니다.
최신 데이터 유지
Firebase 실시간 데이터베이스는 활성 리스너의 데이터를 동기화하고 로컬 사본을 저장합니다. 또한 특정 위치의 동기화를 유지할 수 있습니다.
final scoresRef = FirebaseDatabase.instance.ref("scores");
scoresRef.keepSynced(true);
Firebase 실시간 데이터베이스 클라이언트는 참조에 활성 리스너가 없어도 이러한 위치의 데이터를 자동으로 다운로드하고 동기화합니다. 동기화를 해제하려면 다음 코드를 사용합니다.
scoresRef.keepSynced(false);
기본적으로 이전에 동기화한 데이터 중 10MB가 캐시됩니다. 대부분의 애플리케이션에서는 이 용량으로 충분합니다. 구성된 크기보다 캐시가 커지면 Firebase 실시간 데이터베이스가 가장 오래전에 사용된 데이터를 삭제합니다. 동기화가 유지되는 데이터는 캐시에서 삭제되지 않습니다.
오프라인으로 데이터 쿼리
Firebase 실시간 데이터베이스는 쿼리가 반환한 데이터를 오프라인일 때 사용하기 위해 저장합니다. 오프라인일 때 쿼리를 작성한 경우 Firebase 실시간 데이터베이스는 이전에 로드한 데이터를 사용하여 계속 작동합니다. 요청한 데이터가 로드되지 않으면 로컬 캐시의 데이터가 로드됩니다. 네트워크에 다시 연결되면 데이터가 로드되고 쿼리가 반영됩니다.
다음은 점수를 저장하는 데이터베이스에서 마지막 항목 4개를 쿼리하는 코드 예시입니다.
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}.");
});
연결이 끊겨서 오프라인으로 전환된 후 앱을 다시 시작했다고 가정해 보겠습니다. 계속 오프라인인 상태에서 앱은 같은 위치의 마지막 항목 2개를 쿼리합니다. 앱이 위 쿼리에서 항목 4개를 모두 로드했으므로 이 쿼리로 마지막 항목 2개가 성공적으로 반환됩니다.
scoresRef.orderByValue().limitToLast(2).onChildAdded.listen((event) {
debugPrint("The ${event.snapshot.key} dinosaur's score is ${event.snapshot.value}.");
});
위의 예시에서는 Firebase 실시간 데이터베이스 클라이언트가 지속성 캐시를 통해 최고 점수를 기록한 두 dinosaur에 대해 '하위 요소 추가' 이벤트를 발생시킵니다. 그러나 온라인 상태에서는 해당 쿼리를 실행하지 않았으므로 '값' 이벤트는 발생하지 않습니다.
오프라인 상태인 앱에서 마지막 항목 6개를 쿼리하면 캐시된 항목 4개에 대한 '하위 요소 추가' 이벤트가 즉시 발생합니다. 기기가 다시 온라인으로 전환되면 Firebase 실시간 데이터베이스 클라이언트가 서버와 동기화되고 마지막 2개의 '하위 요소 추가' 및 '값' 이벤트가 발생합니다.
오프라인으로 트랜잭션 처리
앱이 오프라인일 때 수행되는 모든 트랜잭션은 큐에 추가됩니다. 앱이 네트워크에 다시 연결되면 트랜잭션이 실시간 데이터베이스 서버로 전송됩니다.
Firebase 실시간 데이터베이스에는 오프라인 시나리오 및 네트워크 연결 상태에 대응하는 여러 가지 기능이 있습니다. 이 가이드의 나머지 부분은 앱의 지속성 사용 설정 여부에 관계없이 적용됩니다.
접속 상태 관리
실시간 애플리케이션에서는 클라이언트가 연결되거나 연결 해제되는 시점을 감지하면 유용한 경우가 많습니다. 예를 들어 클라이언트 연결이 해제되면 사용자를 '오프라인'으로 표시할 수 있습니다.
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 실시간 데이터베이스 서버에 작업이 상주합니다. 서버는 보안 검사를 실행하여 요청된 쓰기 이벤트를 사용자가 수행할 수 있는지 확인하고 문제가 있으면 앱에 통보합니다. 그런 다음 서버에서 연결을 모니터링합니다. 연결이 타임아웃되거나 실시간 데이터베이스 클라이언트에 의해 종료되면 서버는 보안 검사를 다시 실행하여 작업이 유효한지 재차 확인한 후 이벤트를 호출합니다.
try {
await presenceRef.onDisconnect().remove();
} catch (error) {
debugPrint("Could not establish onDisconnect event: $error");
}
.cancel()
을 호출하여 onDisconnect 이벤트를 취소할 수도 있습니다.
final onDisconnectRef = presenceRef.onDisconnect();
onDisconnectRef.set("I disconnected");
// ...
// some time later when we change our minds
// ...
onDisconnectRef.cancel();
연결 상태 감지
접속 상태 관련 기능에서는 앱의 온라인 또는 오프라인 전환 시점을 확인하면 유용한 경우가 많습니다. Firebase 실시간 데이터베이스는 Firebase 실시간 데이터베이스 클라이언트의 연결 상태가 변경될 때마다 업데이트되는 특수 위치인 /.info/connected
를 제공합니다. 예를 들면 다음과 같습니다.
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
를 읽은 결과가 false이더라도 다른 클라이언트에서는 다른 값으로 읽힐 수 있습니다.
지연 시간 처리
서버 타임스탬프
Firebase 실시간 데이터베이스 서버는 서버에서 생성된 타임스탬프를 데이터로 삽입하는 메커니즘을 제공합니다. 이 기능과 onDisconnect
를 함께 사용하면 실시간 데이터베이스 클라이언트의 연결이 끊긴 시간을 쉽고 정확하게 기록할 수 있습니다.
final userLastOnlineRef =
FirebaseDatabase.instance.ref("users/joe/lastOnline");
userLastOnlineRef.onDisconnect().set(ServerValue.timestamp);
시계 보정값
대부분의 읽기/쓰기 작업에서는 ServerValue.timestamp
가 훨씬 더 정확하고 바람직하지만 Firebase 실시간 데이터베이스 서버를 기준으로 클라이언트 시계 보정값을 측정하면 유용한 경우도 있습니다. 이 값을 밀리초 단위로 가져오려면 /.info/serverTimeOffset
위치에 콜백을 연결합니다. Firebase 실시간 데이터베이스 클라이언트는 이 값을 로컬 보고 시간(밀리초 단위 에포크 시간)에 더하여 서버 시간을 추정합니다. 이 오프셋의 정확성은 네트워킹 지연 시간의 영향을 받을 수 있으므로 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);
}
});