여자친구와 데이트를 해오다보니 "어 저번에 거기 어디였지?" 하는 상황이 종종 오곤했다. 그래서 데이트를 할 때마다 다음에 또 오고 싶은 곳이나 우리만의 비밀 데이트 장소 같은 곳을 저장하는 앱을 개발하려고 한다.
Firebase에서 제공하는 데이터베이스는 CloudFirestore와 RealtimeDataBase가 있었다.
두 개의 특징은 위와 같다.
내가 RealtimeDataBase를 선택한 이유는 앱에서 저장할 데이터들이 아주 단순한 구조이기도 하고, 데이터의 크기가 크지 않기 때문에 고급 수준의 쿼리도 필요없는데다가 비교적 가볍기 때문이다.
RealtimeDataBase는 학부생 때 재미로 채팅앱을 개발했을 때 잠깐 써보고 그 이후로 손도 안 대봤기 때문에 공식문서의 예제로 연습을 했다.
gradle이나 기타 세팅은 공식 문서에 잘 나와있으니 참고하면 될 것 같다.
데이터베이스에 접근하기 위해서는 해당 데이터베이스의 인스턴스가 필요하다.
// 데이터베이스에서 데이터를 읽거나 쓰기 위해
// DatabaseReference 인스턴스 가져오기
val database = Firebase.database
val myRef = database.reference
인스턴스를 가져온 뒤 기본적인 쓰기 작업은 setValue()를 사용하여 데이터를 저장합니다.
이 때 RealtimeDataBase에는 데이터들이 JSON 형태로 저장이 되는데, 지원되는 유형들은 다음과 같습니다.
myRef.setValue("Hello World")
또한 커스텀 객체를 전달할 수도 있는데, 이 때는 public 형태의 getter가 반드시 있어야합니다.
myRef.setValue(User("Gyul",29))
위의 코드는 database.reference를 통해 기본 참조를 가져와 데이터를 저장한 형태이기에 저장된 데이터를 보시면 아래와 같은 구조로 저장이 됩니다.
대부분의 앱 같은 경우 구조 커스텀이 필요한데, 이럴 때는 참조를 직접 설정하여 가져오면 됩니다.
val user = User("Gyul",29)
val customRef = myRef.child("user").child(user.id)
customRef.setValue(user)
저장된 데이터를 읽어올 때는 세가지 방법이 있습니다.
영구 리스너로 읽기, get()을 사용하여 한 번 읽기, 리스너로 한 번 읽어오기
경로를 설정하고 저장된 데이터가 변경될 때마다 즉시 수신을 하려면 addValueEventListener() 메서드를 사용하면 됩니다.
onDataChange() 메서드에서는 경로의 전체 내용을 읽고 변경사항을 수신 대기합니다.
이 메서드는 리스너가 연결될 때 한 번 트리거된 후 하위 요소를 포함한 데이터가 변경될 때마다 다시 트리거됩니다. 하위 데이터를 포함하여 해당 위치의 모든 데이터를 포함하는 스냅샷이 이벤트 콜백에 전달됩니다. 데이터가 없는 경우 스냅샷은 exists() 호출 시 false를 반환하고 getValue() 호출 시 null을 반환합니다.
myRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val value = snapshot.value
tv.text = "${value.toString()}\n"
Log.d(TAG, "onDataChange: $value")
}
override fun onCancelled(error: DatabaseError) {
Log.d(TAG, "onCancelled: ${error.toException()}")
}
})
SDK는 앱이 온라인이든 오프라인이든 상관없이 데이터베이스 서버와의 상호작용을 관리하도록 설계되었습니다.
일반적으로 위에서 설명한 ValueEventListener 기법을 사용하여 데이터를 읽어 백엔드에서 데이터에 대한 업데이트 알림을 수신해야 합니다. 리스너 기법은 사용량과 결제를 줄여주며 사용자가 온라인과 오프라인으로 진행할 때 최상의 환경을 제공하도록 최적화되어 있습니다.
데이터가 한 번만 필요한 경우 get()을 사용하여 데이터베이스에서 데이터의 스냅샷을 가져올 수 있습니다. 어떠한 이유로든 get()이 서버 값을 반환할 수 없는 경우 클라이언트는 로컬 스토리지 캐시를 프로브하고 값을 여전히 찾을 수 없으면 오류를 반환합니다.
불필요한 get() 사용은 대역폭 사용을 증가시키고 성능 저하를 유발할 수 있지만 위와 같이 실시간 리스너를 사용하면 이를 방지할 수 있습니다.
btnGet.setOnClickListener { myRef.get().addOnSuccessListener { tv.text=it.value.toString() } }
경우에 따라 서버의 업데이트된 값을 확인하는 대신 로컬 캐시의 값을 즉시 반환하고 싶을 수 있습니다. 이 경우에는 addListenerForSingleValueEvent를 사용하여 로컬 디스크 캐시에서 데이터를 즉시 가져올 수 있습니다.
이 방법은 한 번 로드된 후 자주 변경되지 않거나 능동적으로 수신 대기할 필요가 없는 데이터에 유용합니다. 예를 들어 위 예시의 블로깅 앱에서는 사용자가 새 글을 작성하기 시작할 때 이 메서드로 사용자의 프로필을 로드합니다.
btnGet.setOnClickListener { myRef.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
tv.text = snapshot.value.toString()
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})}
데이터를 읽어올 때는 계속해서 변경사항을 체크해야할 때는 addValueEventListener를 사용하고, 한 번씩 가져올 때는 addListenerForSingleValueEvent를 사용하면 될 것 같다.