Firebase Summit에서 발표된 모든 내용을 살펴보고 Firebase로 앱을 빠르게 개발하고 안심하고 앱을 실행하는 방법을 알아보세요. 자세히 알아보기

오프라인으로 데이터 액세스

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

Cloud Firestore는 오프라인 데이터 지속성을 지원합니다. 이 기능은 앱이 활발히 사용 중인 Cloud Firestore 데이터의 복사본을 캐시하므로 기기가 오프라인일 때 앱이 데이터에 액세스할 수 있습니다. 캐시된 데이터를 쓰고, 읽고, 듣고, 쿼리할 수 있습니다. 기기가 다시 온라인 상태가 되면 Cloud Firestore는 앱에서 수행한 모든 로컬 변경 사항을 Cloud Firestore 백엔드와 동기화합니다.

오프라인 지속성을 사용하기 위해 Cloud Firestore 데이터에 액세스하는 데 사용하는 코드를 변경할 필요가 없습니다. 오프라인 지속성을 활성화하면 Cloud Firestore 클라이언트 라이브러리가 자동으로 온라인 및 오프라인 데이터 액세스를 관리하고 기기가 다시 온라인 상태가 되면 로컬 데이터를 동기화합니다.

오프라인 지속성 구성

Cloud Firestore를 초기화할 때 오프라인 지속성을 활성화 또는 비활성화할 수 있습니다.

  • Android 및 Apple 플랫폼의 경우 오프라인 지속성이 기본적으로 활성화되어 있습니다. 지속성을 비활성화하려면 PersistenceEnabled 옵션을 false 로 설정합니다.
  • 웹의 경우 오프라인 지속성은 기본적으로 비활성화되어 있습니다. 지속성을 활성화하려면 enablePersistence 메소드를 호출하십시오. Cloud Firestore의 캐시는 세션 간에 자동으로 지워지지 않습니다. 따라서 웹 앱이 민감한 정보를 처리하는 경우 지속성을 활성화하기 전에 사용자에게 신뢰할 수 있는 장치에 있는지 확인해야 합니다.

Web version 9

import { enableIndexedDbPersistence } from "firebase/firestore"; 

enableIndexedDbPersistence(db)
  .catch((err) => {
      if (err.code == 'failed-precondition') {
          // Multiple tabs open, persistence can only be enabled
          // in one tab at a a time.
          // ...
      } else if (err.code == 'unimplemented') {
          // The current browser does not support all of the
          // features required to enable persistence
          // ...
      }
  });
// Subsequent queries will use persistence, if it was enabled successfully

Web version 8

firebase.firestore().enablePersistence()
  .catch((err) => {
      if (err.code == 'failed-precondition') {
          // Multiple tabs open, persistence can only be enabled
          // in one tab at a a time.
          // ...
      } else if (err.code == 'unimplemented') {
          // The current browser does not support all of the
          // features required to enable persistence
          // ...
      }
  });
// Subsequent queries will use persistence, if it was enabled successfully
빠른
참고: 이 제품은 watchOS 및 App Clip 대상에서 사용할 수 없습니다.
let settings = FirestoreSettings()
settings.isPersistenceEnabled = true

// Any additional options
// ...

// Enable offline data persistence
let db = Firestore.firestore()
db.settings = settings
오브젝티브-C
참고: 이 제품은 watchOS 및 App Clip 대상에서 사용할 수 없습니다.
FIRFirestoreSettings *settings = [[FIRFirestoreSettings alloc] init];
settings.persistenceEnabled = YES;

// Any additional options
// ...

// Enable offline data persistence
FIRFirestore *db = [FIRFirestore firestore];
db.settings = settings;

Java

FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
        .setPersistenceEnabled(true)
        .build();
db.setFirestoreSettings(settings);

Kotlin+KTX

val settings = firestoreSettings {
    isPersistenceEnabled = true
}
db.firestoreSettings = settings

Dart

// Apple and Android
db.settings = const Settings(persistenceEnabled: true);

// Web
await db
    .enablePersistence(const PersistenceSettings(synchronizeTabs: true));

캐시 크기 구성

지속성이 사용 설정되면 Cloud Firestore는 오프라인 액세스를 위해 백엔드에서 수신한 모든 문서를 캐시합니다. Cloud Firestore는 캐시 크기에 대한 기본 임계값을 설정합니다. 기본값을 초과한 후 Cloud Firestore는 사용하지 않는 오래된 문서를 주기적으로 정리하려고 시도합니다. 다른 캐시 크기 임계값을 구성하거나 정리 프로세스를 완전히 비활성화할 수 있습니다.

Web version 9

import { initializeFirestore, CACHE_SIZE_UNLIMITED } from "firebase/firestore";

const firestoreDb = initializeFirestore(app, {
  cacheSizeBytes: CACHE_SIZE_UNLIMITED
});

Web version 8

firebase.firestore().settings({
    cacheSizeBytes: firebase.firestore.CACHE_SIZE_UNLIMITED
});
빠른
참고: 이 제품은 watchOS 및 App Clip 대상에서 사용할 수 없습니다.
// The default cache size threshold is 100 MB. Configure "cacheSizeBytes"
// for a different threshold (minimum 1 MB) or set to "FirestoreCacheSizeUnlimited"
// to disable clean-up.
let settings = Firestore.firestore().settings
settings.cacheSizeBytes = FirestoreCacheSizeUnlimited
Firestore.firestore().settings = settings
오브젝티브-C
참고: 이 제품은 watchOS 및 App Clip 대상에서 사용할 수 없습니다.
// The default cache size threshold is 100 MB. Configure "cacheSizeBytes"
// for a different threshold (minimum 1 MB) or set to "kFIRFirestoreCacheSizeUnlimited"
// to disable clean-up.
FIRFirestoreSettings *settings = [FIRFirestore firestore].settings;
settings.cacheSizeBytes = kFIRFirestoreCacheSizeUnlimited;
[FIRFirestore firestore].settings = settings;
  

Java


// The default cache size threshold is 100 MB. Configure "setCacheSizeBytes"
// for a different threshold (minimum 1 MB) or set to "CACHE_SIZE_UNLIMITED"
// to disable clean-up.
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
        .setCacheSizeBytes(FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED)
        .build();
db.setFirestoreSettings(settings);

Kotlin+KTX


// The default cache size threshold is 100 MB. Configure "setCacheSizeBytes"
// for a different threshold (minimum 1 MB) or set to "CACHE_SIZE_UNLIMITED"
// to disable clean-up.
val settings = FirebaseFirestoreSettings.Builder()
        .setCacheSizeBytes(FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED)
        .build()
db.firestoreSettings = settings

Dart

db.settings = const Settings(
  persistenceEnabled: true,
  cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED,
);

오프라인 데이터 듣기

장치가 오프라인일 때 오프라인 지속성을 활성화하면 로컬로 캐시된 데이터가 변경될 때 수신기가 수신 이벤트를 수신합니다. 문서, 컬렉션 및 쿼리를들을 수 있습니다.

서버 또는 캐시에서 데이터를 수신하고 있는지 확인하려면 스냅샷 이벤트의 SnapshotMetadata 에서 fromCache 속성을 사용하십시오. fromCachetrue 인 경우 데이터는 캐시에서 가져온 것이며 오래되었거나 불완전할 수 있습니다. fromCachefalse 이면 데이터는 서버의 최신 업데이트로 완전하고 최신 상태입니다.

기본적으로 SnapshotMetadata 변경된 경우 이벤트가 발생하지 않습니다. fromCache 값에 의존하는 경우 수신 핸들러를 연결할 때 includeMetadataChanges 수신 옵션을 지정하십시오.

Web version 9

import { collection, onSnapshot, where, query } from "firebase/firestore"; 

const q = query(collection(db, "cities"), where("state", "==", "CA"));
onSnapshot(q, { includeMetadataChanges: true }, (snapshot) => {
    snapshot.docChanges().forEach((change) => {
        if (change.type === "added") {
            console.log("New city: ", change.doc.data());
        }

        const source = snapshot.metadata.fromCache ? "local cache" : "server";
        console.log("Data came from " + source);
    });
});

Web version 8

db.collection("cities").where("state", "==", "CA")
  .onSnapshot({ includeMetadataChanges: true }, (snapshot) => {
      snapshot.docChanges().forEach((change) => {
          if (change.type === "added") {
              console.log("New city: ", change.doc.data());
          }

          var source = snapshot.metadata.fromCache ? "local cache" : "server";
          console.log("Data came from " + source);
      });
  });
빠른
참고: 이 제품은 watchOS 및 App Clip 대상에서 사용할 수 없습니다.
// Listen to metadata updates to receive a server snapshot even if
// the data is the same as the cached data.
db.collection("cities").whereField("state", isEqualTo: "CA")
    .addSnapshotListener(includeMetadataChanges: true) { querySnapshot, error in
        guard let snapshot = querySnapshot else {
            print("Error retreiving snapshot: \(error!)")
            return
        }

        for diff in snapshot.documentChanges {
            if diff.type == .added {
                print("New city: \(diff.document.data())")
            }
        }

        let source = snapshot.metadata.isFromCache ? "local cache" : "server"
        print("Metadata: Data fetched from \(source)")
}
오브젝티브-C
참고: 이 제품은 watchOS 및 App Clip 대상에서 사용할 수 없습니다.
// Listen to metadata updates to receive a server snapshot even if
// the data is the same as the cached data.
[[[db collectionWithPath:@"cities"] queryWhereField:@"state" isEqualTo:@"CA"]
    addSnapshotListenerWithIncludeMetadataChanges:YES
    listener:^(FIRQuerySnapshot *snapshot, NSError *error) {
      if (snapshot == nil) {
        NSLog(@"Error retreiving snapshot: %@", error);
        return;
      }
      for (FIRDocumentChange *diff in snapshot.documentChanges) {
        if (diff.type == FIRDocumentChangeTypeAdded) {
          NSLog(@"New city: %@", diff.document.data);
        }
      }

      NSString *source = snapshot.metadata.isFromCache ? @"local cache" : @"server";
      NSLog(@"Metadata: Data fetched from %@", source);
    }];

Java

db.collection("cities").whereEqualTo("state", "CA")
        .addSnapshotListener(MetadataChanges.INCLUDE, new EventListener<QuerySnapshot>() {
            @Override
            public void onEvent(@Nullable QuerySnapshot querySnapshot,
                                @Nullable FirebaseFirestoreException e) {
                if (e != null) {
                    Log.w(TAG, "Listen error", e);
                    return;
                }

                for (DocumentChange change : querySnapshot.getDocumentChanges()) {
                    if (change.getType() == Type.ADDED) {
                        Log.d(TAG, "New city:" + change.getDocument().getData());
                    }

                    String source = querySnapshot.getMetadata().isFromCache() ?
                            "local cache" : "server";
                    Log.d(TAG, "Data fetched from " + source);
                }

            }
        });

Kotlin+KTX

db.collection("cities").whereEqualTo("state", "CA")
        .addSnapshotListener(MetadataChanges.INCLUDE) { querySnapshot, e ->
            if (e != null) {
                Log.w(TAG, "Listen error", e)
                return@addSnapshotListener
            }

            for (change in querySnapshot!!.documentChanges) {
                if (change.type == DocumentChange.Type.ADDED) {
                    Log.d(TAG, "New city: ${change.document.data}")
                }

                val source = if (querySnapshot.metadata.isFromCache)
                    "local cache"
                else
                    "server"
                Log.d(TAG, "Data fetched from $source")
            }
        }

Dart

db
    .collection("cities")
    .where("state", isEqualTo: "CA")
    .snapshots(includeMetadataChanges: true)
    .listen((querySnapshot) {
  for (var change in querySnapshot.docChanges) {
    if (change.type == DocumentChangeType.added) {
      final source =
          (querySnapshot.metadata.isFromCache) ? "local cache" : "server";

      print("Data fetched from $source}");
    }
  }
});

오프라인 데이터 가져오기

기기가 오프라인일 때 문서를 가져오면 Cloud Firestore가 캐시에서 데이터를 반환합니다.

컬렉션을 쿼리할 때 캐시된 문서가 없으면 빈 결과가 반환됩니다. 특정 문서를 가져올 때 대신 오류가 반환됩니다.

오프라인 데이터 쿼리

쿼리는 오프라인 지속성과 함께 작동합니다. 이전 섹션에서 설명한 대로 직접 가져오기 또는 수신 대기를 사용하여 쿼리 결과를 검색할 수 있습니다. 장치가 오프라인 상태일 때 로컬로 지속되는 데이터에 대한 새 쿼리를 만들 수도 있지만 쿼리는 처음에는 캐시된 문서에 대해서만 실행됩니다.

오프라인 쿼리 인덱스 구성

기본적으로 Firestore SDK는 오프라인 쿼리를 실행할 때 로컬 캐시에 있는 컬렉션의 모든 문서를 검색합니다. 이 기본 동작을 사용하면 사용자가 오랜 시간 동안 오프라인 상태일 때 오프라인 쿼리 성능이 저하될 수 있습니다.

Apple 플랫폼 , AndroidJavaScript SDK는 오프라인 쿼리의 성능을 개선하기 위해 로컬 쿼리 인덱스를 구성할 수 있는 setIndexConfiguration 메서드를 제공합니다.

이러한 메서드는 동일한 인덱스 정의 형식 에 따라 서버에서 인덱스를 구성하는 데 사용되는 것과 동일한 JSON 구조의 구성을 읽습니다.

사용할 오프라인 인덱스 구성은 앱이 오프라인에서 많이 액세스하는 컬렉션 및 문서와 원하는 오프라인 성능에 따라 다릅니다. 클라이언트에서 사용하기 위해 백엔드 인덱스 구성을 내보낼 수 있지만 앱의 오프라인 액세스 패턴은 온라인 액세스 패턴과 크게 다를 수 있으므로 온라인 인덱스 구성은 오프라인에서 사용하기에 적합하지 않을 수 있습니다. 앱이 고성능으로 오프라인에서 액세스하기를 원하는 컬렉션과 문서는 무엇입니까? 앱의 동작을 분석했으면 인덱싱 가이드 의 인덱스 정의 원칙을 따르세요.

클라이언트 앱에서 오프라인 인덱스 구성을 로드할 수 있도록 하려면:

  • 앱과 함께 컴파일 및 배포
  • CDN에서 다운로드
  • Firebase용 Cloud Storage 와 같은 스토리지 시스템에서 가져옵니다.

네트워크 액세스 비활성화 및 활성화

아래 방법을 사용하여 Cloud Firestore 클라이언트에 대한 네트워크 액세스를 비활성화할 수 있습니다. 네트워크 액세스가 비활성화된 동안 모든 스냅샷 수신기 및 문서 요청은 캐시에서 결과를 검색합니다. 쓰기 작업은 네트워크 액세스가 다시 활성화될 때까지 대기됩니다.

Web version 9

import { disableNetwork } from "firebase/firestore"; 

await disableNetwork(db);
console.log("Network disabled!");
// Do offline actions
// ...

Web version 8

firebase.firestore().disableNetwork()
    .then(() => {
        // Do offline actions
        // ...
    });
빠른
참고: 이 제품은 watchOS 및 App Clip 대상에서 사용할 수 없습니다.
Firestore.firestore().disableNetwork { (error) in
    // Do offline things
    // ...
}
오브젝티브-C
참고: 이 제품은 watchOS 및 App Clip 대상에서 사용할 수 없습니다.
[[FIRFirestore firestore] disableNetworkWithCompletion:^(NSError *_Nullable error) {
  // Do offline actions
  // ...
}];

Java

db.disableNetwork()
        .addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                // Do offline things
                // ...
            }
        });

Kotlin+KTX

db.disableNetwork().addOnCompleteListener {
    // Do offline things
    // ...
}

Dart

db.disableNetwork().then((_) {
  // Do offline things
});

네트워크 액세스를 다시 활성화하려면 다음 방법을 사용하십시오.

Web version 9

import { enableNetwork } from "firebase/firestore"; 

await enableNetwork(db);
// Do online actions
// ...

Web version 8

firebase.firestore().enableNetwork()
    .then(() => {
        // Do online actions
        // ...
    });
빠른
참고: 이 제품은 watchOS 및 App Clip 대상에서 사용할 수 없습니다.
Firestore.firestore().enableNetwork { (error) in
    // Do online things
    // ...
}
오브젝티브-C
참고: 이 제품은 watchOS 및 App Clip 대상에서 사용할 수 없습니다.
[[FIRFirestore firestore] enableNetworkWithCompletion:^(NSError *_Nullable error) {
  // Do online actions
  // ...
}];

Java

db.enableNetwork()
        .addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                // Do online things
                // ...
            }
        });

Kotlin+KTX

db.enableNetwork().addOnCompleteListener {
    // Do online things
    // ...
}

Dart

db.enableNetwork().then((_) {
  // Back online
});