Firestore에 데이터를 저장할 때 cloud_firestore/not-found 에러가 발생했다. Firestore 경로 설정과 문서 생성 시점의 오류를 분석하여 데이터 저장 기능을 복구했다.
Exception: [cloud_firestore/not-found]
No document found at the specified path.
// 문제가 있던 코드
Future<void> saveUserData(String userId, Map<String, dynamic> data) async {
await FirebaseFirestore.instance
.collection('users')
.doc(userId)
.update(data); // ❌ 문서가 없으면 에러 발생
}
문제점:
update() 메서드는 문서가 이미 존재할 때만 작동문서를 직접 생성하지 않으면 Firestore에 컬렉션 경로가 존재하지 않는다.
❌ Firestore 구조가 비어있는 상태
┌─ collections/
├─ users (존재하지 않음)
└─ ...
✓ 문서 생성 후의 구조
┌─ collections/
├─ users/
│ ├─ userId1/
│ │ └─ (데이터)
│ └─ userId2/
│ └─ (데이터)
update() 대신 set()을 사용하여 문서가 없으면 생성, 있으면 업데이트하도록 변경
// set() 사용: merge 옵션으로 기존 데이터 유지
Future<void> saveUserData(String userId, Map<String, dynamic> data) async {
await FirebaseFirestore.instance
.collection('users')
.doc(userId)
.set(data, SetOptions(merge: true)); // ✓ 문서 없으면 생성
}
Future<void> saveUserData(String userId, Map<String, dynamic> data) async {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(userId)
.set(data, SetOptions(merge: true));
print('데이터 저장 성공');
} catch (e) {
print('저장 실패: $e');
// 사용자에게 알림
}
}
// 1. 단일 데이터 저장
final firestoreService = FirestoreService();
await firestoreService.saveUser(
userId: 'user123',
userData: {
'name': '현서',
'email': 'user@example.com',
'createdAt': FieldValue.serverTimestamp(),
},
);
// 2. 중첩된 데이터 저장
await firestoreService.saveGame(
gameId: 'game001',
gameData: {
'title': '숲코몬',
'region': 'Seoul',
'score': 1000,
'timestamp': FieldValue.serverTimestamp(),
},
);
// 3. 배치 쓰기
await firestoreService.saveBatch([
{'id': 'item1', 'name': '아이템1'},
{'id': 'item2', 'name': '아이템2'},
{'id': 'item3', 'name': '아이템3'},
]);
| 메서드 | 문서 없을 때 | 문서 있을 때 | 사용 시기 |
|---|---|---|---|
| set() | 생성 | 덮어쓰기 | 신규/업데이트 둘 다 |
| update() | 에러 발생 | 부분 업데이트 | 문서가 있을 때만 |
| set() + merge | 생성 | 병합 | 기존 데이터 유지 필요 |
// merge: false (기본값) - 전체 덮어쓰기
await _db.collection('users').doc('user1').set({
'name': '현서',
}); // age는 삭제됨
// merge: true - 기존 데이터와 병합
await _db.collection('users').doc('user1').set({
'name': '현서',
}, SetOptions(merge: true)); // age는 유지됨
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// 사용자는 자신의 데이터만 읽/쓰기 가능
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
}
// 모두 읽기 가능, 인증된 사용자만 쓰기
match /games/{document=**} {
allow read: if true;
allow write: if request.auth != null;
}
}
}
void testSaveUser() async {
final service = FirestoreService();
try {
// 1. 새 문서 생성
await service.saveUser('test_user_1', {
'name': 'Test User',
'email': 'test@example.com',
});
print('✓ 새 문서 생성 성공');
// 2. 기존 문서 업데이트
await service.saveUser('test_user_1', {
'age': 25,
});
print('✓ 문서 업데이트 성공 (기존 데이터 유지)');
// 3. 데이터 검증
final doc = await FirebaseFirestore.instance
.collection('users')
.doc('test_user_1')
.get();
assert(doc.data()?['name'] == 'Test User'); // 기존 데이터 유지
assert(doc.data()?['age'] == 25); // 새 데이터 추가
print('✓ 데이터 검증 성공');
} catch (e) {
print('✗ 테스트 실패: $e');
}
}
// 나쁜 예: 루프마다 개별 저장 (느림)
for (var item in items) {
await _db.collection('items').doc(item['id']).set(item);
}
// 좋은 예: 배치 쓰기 (빠름)
WriteBatch batch = _db.batch();
for (var item in items) {
batch.set(_db.collection('items').doc(item['id']), item);
}
await batch.commit();
복잡한 쿼리는 Firestore에서 인덱스를 자동 생성하도록 안내한다.
// 인덱스가 필요한 쿼리
_db.collection('users')
.where('region', isEqualTo: 'Seoul')
.where('level', isGreaterThan: 10)
.orderBy('score', descending: true)
.get();
cloud_firestore/not-found 에러는 주로 잘못된 저장 메서드 사용 또는 경로 오류에서 발생한다. set() 메서드와 SetOptions(merge: true)를 올바르게 사용하면 안정적인 데이터 저장이 가능하다. 경로를 상수화하고 에러 처리를 추가하면 유지보수성도 높아진다.