본문 바로가기

안드로이드 Android

안드로이드 개발(2) -파베 RealtimeDatabase 사용 팁

안녕하세요 Loner입니다.

다른 블로그보면 Toy 프로젝트 하나 만들어서 코드를 멋지게 예시를 드는데,,

평소에 할일이 많아서 Toy 프로젝트 만들기가 버겁네요..

(아니면 내가 불성실한거거나..)

 

그래서 사이드 협업에서 사용하는 프로젝트의 코드나 경험들을 가지고 블로그에 기록들을 남기는중 입니다.

나중에 내 블로그를 보면서 생각 정리가 잘되는 날이 왔으면 좋겠네요. 

 

방금 막 파이어베이스로 게시판을 만들어야하는 상황이 있었는데

잘 끝내서 작게 포스팅하나 남겨보도록 하겠습니다.  

 

해당 프로젝트는 JAVA로 진행됬고, 옛감성을 느끼고자 findByViewId를 위주로 사용하는 

안드 덕후들의 변태감성 프로젝트라고 보시면 됩니다.

 

- 파베 리얼타임 베이스 사용 팁-

파이어베이스 리얼타임 베이스의 특징은 json 형식과 같은 트리형태로 이루어진 데이터 구조를 가집니다. 

그리고 기본적으로 양방향 통신을 지원해줍니다. 하지만 리얼타임 베이스의 단점으로,, 쿼리가 매우 투박하다는 점과 

평소 http 통신에 익숙한 사람이라면 양방향 통신에 반복적으로 ui가 갱신되는 경우가 있습니다.

 

1) 실시간 통신을 할 생각이 없다면 반드시 한번만 호출하자

리얼타임 베이스에서 addListenerForSingleValueEvent 라는 한번만 데이터를 받고

연결을 닫아버리는 함수를 지원해줍니다. 

 

notificationAdapter.setOnItemClick(board -> {
Contents.userDB().child(getString(R.string.admin_idx)).addListenerForSingleValueEvent(
new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
....

NoticeBoardNotificationAdapter notificationAdapter =

 new NoticeBoardNotificationAdapter(noticeBoards);

}

.....

그러면 http 통신 하듯이 통신을 할 수가 있습니다.

위 코드는 서버에서 값을 받아 리싸이클러뷰를 사용할 어댑터를 설정해주는 부분 입니다. addListenerForSingleValueEvent() 를 쓰지 않고 

addValueEventListener() 를 사용한다면 해당 DB가 바뀔때마다 계속 저 함수가 호출되는데,,

문제는 만약 멤버변수로 list변수를 하나둬서 하는 경우에 list에 추가가 .add가 되기 떄문에 리스트의 아이템이

두배로 늘어나는 사태가 간혹 일어나기도 하고, 

런타임에서 프로그램이 돌아갈 때 잘 분기처리를 안해줬다면, 저 실시간 응답 때문에

타입이 안맞거나 널포인트 익셉션으로 인해서 앱이 도중에 죽기도 합니다.

 

반드시 addValueEventListener()는 효율적으로 사용해야합니다.(채팅, 실시간 응답필요 할때 등등..)

addValueEventListener()의 비중을 줄여서 예외처리에 많은 힘이 안가도록 주의해야합니다. 

 

그리고 최대한 addListenerForSingleValueEvent()의 비중을 많이 늘려서 머릿속으로 어디는 한번 수신, 

어디는 실시간 통신임을 정확히 구분해서 사용하는것이 유지보수하기에도 편합니다. 

 

2) DB 만들때 반드시 신중하게 

보통 파베 리얼타임 데이타베이스는 작은 프로젝트를 하거나 샘플 프로젝트를 할 때

혹은 일단 만들어 보고 나중에 json 추출해서 서버 옮기기 위해 등등으로 사용이 됩니다. 

 

그래서 간단히 생각하고 DB를 가볍게 만들었다가 혼줄이 나는경우가 있습니다.

왜냐하면 당연히 noSql이라서 join이 없는것은 당연한것이고 쿼리가 상당히 투박하기도 합니다. 

그리고 다른 테이블을 조회할 id 목록들을 한 필드에 담기도 하는데 

 

그렇게 사용하게 되면 비동기 특성상 콜백안에 콜백을 타는 콜백 지옥이 시작됩니다.

콜백안에 콜백을 부르는 정신 없는 상황을 초래하게 됩니다. 가독성이 상당히 떨어지기 시작하고, 정신 없는 콜백에서 내가 데이터를 어떻게 처리했는지 기억이 잘 안나는 상황이 다가옵니다.

 

tv_numberOfParticipants.setText(String.valueOf(toDayMissionUsers.size()));
setMissionUserListAdapter();
setPastMissionListAdapter();

 

그래서 차라리 UI갱신은 콜백 가장 안쪽 마지막부분에서 시작하는게 가독성상 낫기도 합니다. 

(작은 미세한 성능 차이가 있겠지만 그건 개발자 취향대로..)

 

위와 같은 상황을 늘 피할수는 없겠지만 어떤 부분에 있어서 단점이 있는지 잘 파악해서

db 만드는데 신중을 기하고 제작을 들어가는게 편합니다.

 

3)주로 쓰는 DB 레퍼런스는 enum으로 처리하자.

파이어베이스를 사용하다보면.. 

파이어베이스를 싱글톤으로 가져와서 레퍼런스를 스트링 값으로 넣어 사용합니다. 간혹 초심자분들이 

하드 코딩으로 일일히 String을 적는 경우가 있는데, 그런 방식보다 

고정되는 레퍼런스 스트링값은 이넘클래스를 만들어 사용하는것이 편합니다.

 

위와 같은 레퍼런스 키들이 있다고 한다면 아래와 같이 정의해두고 사용하면 편합니다.

 

public enum FBRef{
USER,MISSION,USER_MISSION,REPLY,BOARD
}

 

 

 

db.getReference("Mission").setValue(). X

db.getReference(FBRef.Mission).setValue(). O

 

불변하는 값이라서 실수로 스트링을 잘못 적어서 서버에 값을 잘못보내는 불상사가 없어집니다. 

더 보안을 생각한다면 string Resource에 저장해둬서 사용하는것도 상당히 좋은 방법 입니다.

절대 String으로 사용하는 일은 없도록 주의하길 바랍니다.. 

 

4) orderByChild.equalTo.limitToFirst() 콤보를 최대한 활용하자

 

Contents.missionDB().orderByChild("finish").equalTo(false)

 

파베를 사용할 때 레퍼런스의 데이터를 모두 가져와서 목록을 사용하는 경우가 많습니다. 

하지만 모두 데이터를 가져올 필요가 없을때 limitToFirst() 로 가져올 갯수를 지정할 수 있습니다.

그리고 orderByChild.equalTo 를 통해서

클라에서 한번더 sort를 할 필요없이 아예 정렬되는 값을 가져와 사용할 수 있습니다.

명심해야 할것은 파이어베이스만 사용해서 통신한다고 했을 때 당신의 앱은 파베로 인한 비동기 코드들이 

당신의 앱을 점령할겁니다.

그런 상황속에서 최대한 Query이나 정렬을 이용해서 이미 정리된 데이터를 가져오는것으로 

코드를 최적화해야 나중에 유지보수를 하거나 새로운 기능을 넣을 때 더 안정적으로 진행할 수 있을겁니다.

 

5) RecyclerView와 addValueEventListener() 를 사용할 때

addValueEventListener()는 db가 변경될 때마다 반복적으로 호출 됩니다.

언제나 바라보는 db가 변경이 없는지 바라보고 있습니다. 

 

addValueEventListener에 따라 리싸이클러뷰를 여러번 갱신해야 한다고 할 때,

클래스의 멤버변수로 ArrayList를 어댑터 인자로 넣는 것이라면 주의해야합니다. 

addValueEventListener() 가 호출 될때마다 ArrayList.add()가 호출된다하면,

인덱스 사이즈가 두배로 늘어나서 리싸이클러뷰 아이템 수가 전체 복사 붙여넣기 하듯이 두배로 늘어납니다.

 

replies <-클래스 멤버 변수for (DataSnapshot ds : snapshot.getChildren()) {
Reply reply = ds.getValue(Reply.class);
assert reply != null;
if (reply.getBoardIdx().equals(board.getBoardIdx())) {
replies .add(reply);
   }
}

위 같이 쓰면replies의 사이즈가 라이프 사이클상 두배가 되기도 한다 ㄷㄷ... 

--------------------------------------------------

아래와 같은 방식으로 사용해주거나, 휘발성인 지역 변수만 사용하는것을 권장합니다. 

replies <-클래스 멤버 변수

//temp를 만듭니다.

ArrayList<Reply> tempList = new ArrayList<Reply>();
for (DataSnapshot ds : snapshot.getChildren()) {
Reply reply = ds.getValue(Reply.class);
assert reply != null;
if (reply.getBoardIdx().equals(board.getBoardIdx())) {
tempList.add(reply);
}
}
replies = tempList;

 

6) 뷰모델, 라이브데이타와 파이어베이스 

요즘 많은 주니어 개발자들도 ViewModel, LiveData를 필수로 사용하고 애용하고 있는거 같습니다.

aac라이브러리를 사용한다고 한다면, 파이어베이스도 ViewModel과 LiveData 같이 써주면 좋습니다.

 

private val _noticeBoardList = MutableLiveData<List<NoticeBoard>>()
val noticeBoardList: LiveData<List<NoticeBoard>>
get() = _noticeBoardList



fun getNoticeBoardList() {

    db.getReference(FBRef.Mission).addListenerForSingleValueEvent{ 

.....생략
    _noticeBoardList.postValue(value?.toObjects(NoticeBoard::class.java)?.toList())
}

데이터 기반 비즈니스 로직은 ViewModel에서 담당하게 만들었을 시에 

LiveData와 같이 써준다면 좋습니다.

비동기의 결과를 LiveData가 받아서 액티비티나 프레그먼트단에 결과를 뿌려주는식으로 사용한다면 

(마치 레트로핏 사용하는것처럼..) 뷰는 결과만 받고, 뷰모델은 비즈니스 로직을 담당하게 됩니다.

(여기서 레포지토리 패턴까지 더 한다면 금상첨화)

 

vm!!.noticeBoardList.observe(requireActivity(), {
    rvPostList.animationRemove()
     val list = it.sortedBy { it.timestamp?.seconds }
     vpNoticeBoardNotification.adapter = NoticeBoardAdapter(list, NOTIFICATION_ADAPTER)
     rvPostList.adapter = NoticeBoardAdapter(list, LIST_ADAPTER)
}) 

앞서 말했다싶히 addListenerForSingleValueEvent() 를 적극 활용한다면 마치 http 통신을 하는듯한 착각도 느낄 수 있습니다. 

 

이상 파베 팁 정리 였습니다.