Firebase - Realtime Database 정리

dongbin is free·2023년 1월 24일
0

Android

목록 보기
2/6

서론

최근 진행하고 있는 작은 프로젝트에서 데이터베이스로 Firebase의 Realtime Database를 사용하기로 했다. 가장 마지막으로 파이어베이스를 사용했던게 2년도 더 된 것 같아 어떻게 사용했는지 기억이 잘 나지 않아서 정리해보려고 한다.

Realtime Database 선택이유

이번에 진행중인 프로젝트에 사용할 데이터베이스로서 Firebase의 Realtime Database와 Firestore를 두고 어떤 것을 사용할지 많은 고민이 있었다. 실시간 DB vs Firestore을 읽어보고, 진행하려는 프로젝트와 비슷한 기능이 구현된 레퍼런스 자료들을 찾아본 결과 자료가 더 많고, 사용해본 경험이 있는 실시간 DB를 한번 더 사용하기로 했다. (추후에 Firestore도 사용해볼 계획이다.)

시작하기 앞서

파이어베이스에 프로젝트를 생성하여 앱 등록을 하고, app수준 gradle에 의존성을 추가하고, google-services.json 파일을 추가하는 등의 연동 과정은 조금만 찾아보아도 많이 나오기때문에 필자는 데이터베이스의 기본적인 작업인 CRUD 방식과 이에 따라오는 리스너들의 개념 위주로 정리해볼까 한다.

사용하기

1. Database 접근을 위한 DataSource 얻기

아래와 같은 데이터베이스 구조에서 chatRooms에 접근해보려고 한다.

// Database Instance 가져오기
val database = Firebase.database
// Database 위치 참조 예시
val reference = database.reference
val chatRoomsReference = database.child("ChatRoom").child("chatRooms")

이후 CRUD 작업에 대한 처리는 아래와 같이 진행한다.

2. Data 쓰기(CREATE)

setValue() 메서드를 통해 지정된 위치에 데이터를 추가할 수 있다. 이 때 하위 노드를 포함한 모든 데이터를 덮어쓴다.

fun setValue(value: Any?): Task<Void!>
👉 해당 위치의 데이터를 지정된 값으로 설정 null을 전달 시 지정된 위치의 데이터가 삭제됨
👉 가능한 Native Type은 JSON 타입에 대응 : Boolean, String, Long, Double, Map, List
👉 맞춤 class를 사용하여 setValue()의 인자로 객체를 넣어 보내줄 수도 있다.

fun setValue(value: Any?, listener: DatabaseReference.CompletionListener?): Unit
👉 작업 결과에 따른 트리거되는 리스너를 추가

// data class 정의
data class Chat(val uid: String? = null, val message: String? = null, val time: String? = null, val name: String?) {
    constructor() : this("", "", "", "")
}
// 데이터 쓰기
val chat = Chat("1234", "hi", "현재 시각", "염동빈")
chatRoomReference.setValue(chat)

👉 setValue()전에 push()를 통해 랜덤한 문자열을 key로 할당하여 목록을 만들어 넣을 수 있다.
chatRoomReference.push().setValue(chat)

3. Data 읽기(READ)

실시간으로 데이터를 읽기 위해서는 Database Reference에 아래 3가지 방법으로 Listener를 추가하며 읽기 동작의 오버헤드를 줄이고 성능을 높이기 위해 용도에 맞는 적절한 방식을 선택하여야 한다.

addValueEventListener( ) 메소드를 이용하여 ValueEventListener 추가
👉 경로 내 데이터 변경 시 모든 데이터를 받아오는 경우 사용

addListenerForSingleValueEvent( ) 메소드를 이용하여  ValueEventListener 추가
👉 초기 1번만 받아오고, 이후 데이터 변경이 없는 경우 사용

chatRoomsReference.addValueEventListener(object: ValueEventListener{
    override fun onDataChange(snapshot: DataSnapshot) {
		// 데이터의 스냅샷과 함께 호출, 데이터가 변경될 때마다 호출
    }
    override fun onCancelled(error: DatabaseError) {
		// 서버에서 장애가 발생했거나 보안 및 Firebase DB 규칙에 의해 삭제되었을 때 호출
    }
})
chatRoomsReference.addListenerForSingleValueEvent(object: ValueEventListener{
    override fun onDataChange(snapshot: DataSnapshot) {
		// 데이터의 스냅샷과 함께 호출, 처음 한번만 호출
    }
    override fun onCancelled(error: DatabaseError) {
		// 서버에서 장애가 발생했거나 보안 및 Firebase DB 규칙에 의해 삭제되었을 때 호출
    }
})

addChildEventListener( ) 메소드를 이용하여 ChildEventListener 추가
👉 매번 전체 데이터를 읽어올 필요없이 새 데이터만 필요한 경우 사용  

chatRoomsReference.addChildEventListener(object: ChildEventListener {
    override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
        // 새 하위 항목이 추가될 때 호출
    }
    override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
        // 하위 위치의 데이터가 변경되었을 때 호출
    }
    override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
        // 하위 위치의 우선 순위가 변경될 때 호출
    }
    override fun onChildRemoved(snapshot: DataSnapshot) {
        // 하위 항목이 제거될 때 호출
    }
    override fun onCancelled(error: DatabaseError) {
        // 서버에서 장애가 발생했거나 보안 및 Firebase DB 규칙에 의해 삭제되었을 때 호출
    }
})

get() 메서드를 통해 1회성으로 DataSnapshot을 얻을 수도 있다.

chatRef.get().addOnSuccessListener {

}

이벤트 리스너를 통해 데이터를 읽어왔으니 데이터를 다루는 방법에 대해 알아보자

데이터는 DataSnapShot 형태로 받아오게 된다. 해당 스냅샷에 아래와 같은 메서드를 통해서 원하는 데이터를 추출할 수 있다.

fun child(path: String): DataSnapshot
👉 지정된 상대 경로의 위치에 대한 DataSnapshot을 가져옴
👉 하위 위치에 데이터가 없으면 빈 DataSnapshot 반환

fun exists(): Boolean
👉 해당 snapshot이 null이 아닌 값을 포함 시 true 반환

fun getChilderen(): (Mutable)Iterable<DataSanapshot!>
👉 해당 shapshot의 모든 직계 하위 항목에 대한 접근 가능
👉 반복문에 사용 가능 → for(child in parent.children) { .. }

fun getKey(): String?
👉 해당 snapshot의 소스 위치에 대한 키 이름을 반환, 스냅샷이 루트를 가리키는 경우 null 반환

fun getPriority( ): Any?
👉 해당 snapshot에 포함된 데이터의 우선 순위를 기본 유형으로 반환
👉 Double, String 형태로 반환 가능

fun getRef( ): DatabaseReference
👉 해당 snapshot의 소스 위치에 대한 참조를 반환

fun getValue(): Any?
👉 해당 snapshot에 포함된 데이터를 native type으로 반환
👉 Boolean, String, Long, Double, Map, List type으로 반환 가능

fun <T> getValue(valueType: Class<T!>): T?
👉 해당 snapshot class에 포함된 데이터를 marshall data으로 반환 가능 2가지 제약조건이 존재
  1. class는 arguments를 사용하지 않는 디폴트 생성자가 있어야 함
  2. class는 반드시 public getter를 정의해야 함 역직렬화된 경우 public getter가 없는 속성이 기본값으로 설정

fun hasChild(path: String): Boolean
👉 DataSnapshot이 특정 위치에서 데이터가 있는지 여부를 반환

fun hasChilderen(): Boolean
👉 해당 snapshot에 하위 항목이 있는지 여부를 반환

3. Data 갱신(UPDATE)

fun updateChildren(update: (Mutable)Map<String!, Any!>): Task<Void!>
👉 특정 하위 키를 지정된 값으로 업데이트, null을 전달 시 지정된 위치에서 값이 삭제됨

fun updateChildren(update: (Mutable)Map<String!, Any!>,
				listener: DatabaseReference.CompletionListener?): Unit
👉 작업 결과에 따른 트리거되는 리스너를 추가

4. Data 지우기(DELETE)

fun removeValue(): Task<Void!>
👉 해당 위치의 값을 null로 설정

fun removeValue(listener: DatabaseReference.CompletionListener?): Unit
👉 작업 결과에 따른 트리거되는 리스너를 추가

5. Query 다루기

양이 너무 많아 Firebase 공식 레퍼런스를 참고하여 필요할 때마다 찾아보며 숙달하기로 하자..

결론

너무 많은 기능은 사람을 힘들게 한다...
코루틴을 공부해볼까 한다.

profile
배운 것을 적어나가는 그런 공간.. 적다 보면 또 까먹는 그런 사람..

0개의 댓글