데이터베이스에서 데이터를 읽거나 쓰려면 DatabaseReference 인스턴스가 필요하다.
DatabaseReference ref = FirebaseDatabase.instance.ref();
Firebase 데이터는 DatabaseReference에 기록되고, 참조에서 내보낸 이벤트를 대기하거나 리슨하여 검색한다. 이벤트를 데이터의 초기 상태에 한 번 내보내고 데이터가 변경될 때마다 다시 내보낸다.
기본 쓰기 작업의 경우 set()을 사용하여 지정된 참조에 데이터를 저장하고 해당 경로의 기존 데이터를 모두 바꾼다. String, boolean, int, double, Map, List 유형으로 참조를 설정할 수 있다.
예를 들어 다음과 같이 set()로 사용자를 추가할 수 있다.
DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");
await ref.set({
"name": "John",
"age": 18,
"address": {
"line1": "100 Mountain View"
}
});
이 방법으로 set()를 사용하면 지정된 위치에서 하위 노드를 포함하여 모든 데이터를 덮어쓴다. 그러나 전체 객체를 다시 쓰지 않고도 하위 항목을 업데이트하는 방법이 있다. 사용자가 프로필을 업데이트하는 것을 허용하려면 다음과 같이 사용자 이름을 업데이트할 수 있다.
DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");
// Only update the name, leave the age and address!
await ref.update({
"age": 19,
});
update() 메서드는 노드에 대한 하위 경로를 허용하여 데이터베이스의 노드 여러 개를 한 번에 업데이트할 수 있다.
DatabaseReference ref = FirebaseDatabase.instance.ref("users");
await ref.update({
"123/age": 19,
"123/address/line1": "1 Mountain View",
});
경로의 데이터를 읽고 변경사항을 리슨하려면 DatabaseReference의 onValue 속성을 사용하여 DatabaseEvent를 리슨한다.
DatabaseEvent를 사용하면 이벤트 발생 시점에 존재하는 지정된 경로에서 데이터를 읽을 수 있다. 이 이벤트는 리스너가 연결될 때 한 번 트리거된 후 하위 요소를 포함하여 데이터가 변경될 때마다 다시 트리거된다. 이벤트에는 하위 데이터를 포함하여 해당 위치의 모든 데이터를 포함하는 snapshot 속성이 있다. 데이터가 없으면 스냅샷의 exists 속성은 false이고 value 속성은 null이다.
DatabaseReference starCountRef =
FirebaseDatabase.instance.ref('posts/$postId/starCount');
starCountRef.onValue.listen((DatabaseEvent event) {
final data = event.snapshot.value;
updateStarCount(data);
});
리스너는 이벤트 발생 시점에 데이터베이스에서 지정된 위치에 있는 데이터를 포함한 DataSnapshot을 value 속성에 수신.
데이터가 한 번만 필요한 경우 get()을 사용하여 데이터베이스에서 데이터의 스냅샷을 가져올 수 있다. 어떠한 이유로든 get()가 서버 값을 반환할 수 없는 경우 클라이언트는 로컬 스토리지 캐시를 프로브하고 값을 여전히 찾을 수 없으면 오류를 반환.
다음 예시에서는 사용자의 공개 사용자 이름을 데이터베이스에서 한 번 검색하는 방법을 보여준다.
final ref = FirebaseDatabase.instance.ref();
final snapshot = await ref.child('users/$userId').get();
if (snapshot.exists) {
print(snapshot.value);
} else {
print('No data available.');
}
💡 불필요한
get()사용은 대역폭 사용을 증가시키고 성능 저하를 유발할 수 있지만 위와 같이 실시간 리스너를 사용하면 이를 방지할 수 있다.
경우에 따라 서버의 업데이트된 값을 확인하는 대신 로컬 캐시의 값을 즉시 반환하고 싶을 수 있다. 이 경우에는 once()를 사용하여 로컬 디스크 캐시에서 데이터를 즉시 가져올 수 있다.
이 방법은 한 번 로드된 후 자주 변경되지 않거나 능동적으로 수신 대기할 필요가 없는 데이터에 유용하다. 예를 들어 위 예시의 블로깅 앱에서는 사용자가 새 글을 작성하기 시작할 때 이 메서드로 사용자의 프로필을 로드한다.
final event = await ref.once(DatabaseEventType.value);
final username = event.snapshot.value?.username ?? 'Anonymous';
다른 하위 노드를 덮어쓰지 않고 특정 하위 노드에 동시에 쓰려면 update() 메서드를 사용.
update()를 호출할 때 키 경로를 지정하여 더 낮은 수준의 하위 항목 값을 업데이트할 수 있다. 확장성 개선을 위해 데이터를 여러 위치에 저장한 경우 데이터 팬아웃을 사용하여 해당 데이터의 모든 인스턴스를 업데이트할 수 있다. 예를 들어 소셜 블로깅 앱에서 게시물을 생성한 후 최근 활동 피드 및 게시자의 활동 피드에 동시에 업데이트해야 할 수 있다. 이러한 경우 블로깅 애플리케이션에서는 다음과 같은 코드를 사용한다.
void writeNewPost(String uid, String username, String picture, String title,
String body) async {
// A post entry.
final postData = {
'author': username,
'uid': uid,
'body': body,
'title': title,
'starCount': 0,
'authorPic': picture,
};
// Get a key for a new Post.
final newPostKey =
FirebaseDatabase.instance.ref().child('posts').push().key;
// Write the new post's data simultaneously in the posts list and the
// user's post list.
final Map<String, Map> updates = {};
updates['/posts/$newPostKey'] = postData;
updates['/user-posts/$uid/$newPostKey'] = postData;
return FirebaseDatabase.instance.ref().update(updates);
}
이 예시에서는 push()를 사용하여 모든 사용자의 게시물을 포함하는 노드(/posts/$postid)에서 게시물을 작성하는 동시에 key로 키를 검색한다. 그런 다음 이 키를 사용하여 /user-posts/$userid/$postid에서 사용자의 게시물에 두 번째 항목을 작성한다.
이 경로를 사용하면 이 예시에서 두 위치에 새 게시물을 생성한 것처럼 update()를 한 번만 호출하여 JSON 트리의 여러 위치에서 동시에 업데이트를 수행할 수 있다. 이러한 동시 업데이트는 원자적이다. 즉, 모든 업데이트가 성공하거나 모든 업데이트가 실패한다.
데이터를 삭제하는 가장 간단한 방법은 해당 데이터 위치의 참조에 remove()를 호출하는 것이다.
null을 update() 또는 set()와 같은 다른 쓰기 작업 값으로 지정하여 삭제할 수도 있다. update()에 이 방법을 사용하면 API 호출 한 번으로 여러 하위 항목을 삭제할 수 있다.