Android/Firebase

[JAVA] 채팅앱 만들기(2) - Pagination, RealtimeDB query

kim_jin 2024. 1. 4. 11:24

안녕하세요 ~ ^^

오늘은 좀 빨리 왔습니다 ㅎㅎ 

 

저는 채팅방에 들어올 때마다! 최신 메세지 30개를 불러오고, 최상단으로 recyclerview를 스크롤 하면 그 상위의 30개 메세지를 더 불러오는  형식으로 진행했었는데요 ㅋㅋ

 

첨부터 다 불러오고 db에 저장하는 형식으로 할 껄 그랬습니다 ~ ㅋㅋ 

 

그치만 페이지네이션 관련해서도 설정하느랴 애먹었으니

페이지네이션 관련 해서도 집중해서 봐주셔도 좋을 것 같습니다!

Message message = new Message(content, ServerValue.TIMESTAMP, userUid, userName);

DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference();
databaseReference.child("channels/MainChattingRoom/messages").push().setValue(message);

저는 메세지를 push방식으로 저장했습니다. (이전글 참고)

 

 

https://firebase.google.com/docs/database/admin/save-data?hl=ko#section-set

 

RealtimeDB관련해서 공식문서에  목록 항목이 시간순으로 자동 정렬 된다고 써 있으므로 전 키값을 기준으로 메세지를 불러올 것입니다!

Query query = FirebaseDatabase.getInstance().getReference("channels")
        .child("MainChattingRoom").child("messages")
        .orderByKey()
        .limitToLast(ITEM_LOAD_COUNT);

 

키값으로 정렬한 후, 마지막 ITEM_LOAD_COUNT 개수만큼 불러올 수 있는 쿼리를 작성해줍니다. 전 30으로 설정했어요.

제일 마시막 메세지부터 하나씩 순서대로 불러올 수 있습니다. 

query.addChildEventListener(new ChildEventListener() {
    int i = 0;
    @Override
    public void onChildAdded(@NonNull DataSnapshot snapshot, @Nullable String previousChildName) {
        flag = true;
        Message message = snapshot.getValue(Message.class);

        int msgSize = messageArrayList.size();

        String xDate;
        String tDate;

        if(msgSize == 0){
            xDate = "";
        }else {
            xDate = getTimestampToDate(messageArrayList.get(msgSize-1).getDate());
        }
        tDate = getTimestampToDate(message.getDate());

        if(!xDate.equals(tDate)){
            Message message1 = new Message(message.getContent(), message.getDate(), message.getSenderId(), message.getSenderName());
            message1.setViewType(2);
            messageArrayList.add(message1);
        }

        if (Objects.equals(message.getSenderId(), userUid)) {
            message.setViewType(0);
            messageArrayList.add(message);
        } else{
            message.setViewType(1);
            messageArrayList.add(message);
        }
        if(i==0){
            first_node = snapshot.getKey();
        }
        i=i+1;
        messageAdapter.submitData(messageArrayList);
        recyclerView.scrollToPosition(messageArrayList.size()-1);
    }

    @Override
    public void onChildChanged(@NonNull DataSnapshot snapshot, @Nullable String previousChildName) {}

    @Override
    public void onChildRemoved(@NonNull DataSnapshot snapshot) {}

    @Override
    public void onChildMoved(@NonNull DataSnapshot snapshot, @Nullable String previousChildName) {}

    @Override
    public void onCancelled(@NonNull DatabaseError error) {}
});

 

이 쿼리에 addChildEventListener 를 설정함으로써, 새로 추가되는 메세지 또한 리사이클러뷰에 추가될 수 있도록 설정했습니다. (하위 노드에 대한 변경 사항을 감지)

 

xDate에는 이전 메세지의 시간 값을 저장하고, tDate에는 현재 메세지의 시간 값을 저장해줍니다.

    private static String getTimestampToDate(Object timestamp) {
        Date date = new Date((Long) timestamp);

        SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy.MM.dd (E)");
        sdf.setTimeZone(java.util.TimeZone.getTimeZone("GMT+9"));
        String formattedDate = sdf.format(date);

        return formattedDate;
    }

 

년.월,일 (요일) 형식으로 타임스탬프 값을 바꿔서 비교함으로써, 날짜가 변경됐는지 확인했습니다.

 

이전 메세지의 시간 값과 현재 메세지의 값이 다를 경우, messageArrayList에 viewType을 2로 설정해서 값을 넣어줬습니다. 

그 이후, 현재 메세지를 넣어줍니다. uid를 비교해서 내가 보낸 메세지인지, 남이 보낸 메세지인지 구분했습니다.

 

first_node = snapshot.getKey();

 

이렇게 첫번째 키를 저장한 것은 페이지네이션 부분에서 활용됩니다.

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);

        // 최상단에 있을 때 메세지 불러오기
        if(!recyclerView.canScrollVertically(-1) && flag){
            getMessages();
        // 최하단에 있을 때 메세지 하단으로 내리는 버튼 삭제
        } else if (!recyclerView.canScrollVertically(1) && flag) {
            btnDown.setVisibility(View.GONE);
        } else{
            // 최하단에 있을 때 메세지 하단으로 내리는 버튼 삭제
            btnDown.setVisibility(View.VISIBLE);
        }

    }

    @Override
    public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        currentitems = recyclerView.getChildCount();
        tottalitems = messageAdapter.getItemCount();
        scrolledoutitems = layoutManager.findFirstCompletelyVisibleItemPosition();
    }
});

 

private void getMessages() {
    Query query;
    // 저장된 first_node 값을 기준으로 ITEM_LOAD_COUNT 만큼 불러옴
    query = FirebaseDatabase.getInstance().getReference("channels")
            .child("MainChattingRoom").child("messages")
            .orderByKey()
            .endBefore(first_node)
            .limitToLast(ITEM_LOAD_COUNT);

    query.addListenerForSingleValueEvent(new ValueEventListener() {

        @Override
        public void onDataChange(@NonNull DataSnapshot snapshot) {
            // 이 쿼리에 값이 있을 때만 동작
            if(snapshot.hasChildren()){
                ArrayList <Message> newMsgArray = new ArrayList<>();

                int i = 0;
                for(DataSnapshot dataSnapshot : snapshot.getChildren()){
                    Message message = dataSnapshot.getValue(Message.class);
                    int msgSize = newMsgArray.size();

                    String xDate;
                    String nDate;
                    String currentDate;

                    xDate = getTimestampToDate(messageArrayList.get(0).getDate());
                    currentDate = getTimestampToDate(message.getDate());

                    if (messageArrayList.get(0).getViewType() == 2 && xDate.equals(currentDate)){
                        messageArrayList.remove(0);
                        messageAdapter.submitData(messageArrayList);
                        messageAdapter.notifyDataSetChanged();
                    }

                    if(msgSize == 0){
                        nDate = "";
                    }else {
                        nDate = getTimestampToDate(newMsgArray.get(msgSize - 1).getDate());
                    }

                    if(!nDate.equals(currentDate)){
                        Message message1 = new Message(message.getContent(), message.getDate(), message.getSenderId(), message.getSenderName());
                        message1.setViewType(2);
                        newMsgArray.add(message1);
                    }

                    if (Objects.equals(message.getSenderId(), userUid)) {
                        message.setViewType(0);
                        newMsgArray.add(message);
                    } else{
                        message.setViewType(1);
                        newMsgArray.add(message);
                    }
                    if (i == 0){
                        x_first_node = first_node;
                        first_node = dataSnapshot.getKey();
                    }
                    i=i+1;
                }

                // 첫번째 노드까지 다 불러왔을 경우엔 더 불러오지 않음
                if (first_node.equals(first_key) && isMax){

                } else if (!Objects.equals(x_first_node, first_node)){
                    messageArrayList = (ArrayList<Message>) Stream.concat(newMsgArray.stream(), messageArrayList.stream()).collect(Collectors.toList());
                    messageAdapter.submitData(messageArrayList);
                    messageAdapter.notifyDataSetChanged();
                    layoutManager.scrollToPositionWithOffset(ITEM_LOAD_COUNT,0);
                }

                if (first_node.equals(first_key)){
                    isMax = true;
                }
            }else{
                isMaxData = true;
            }
        }

        @Override
        public void onCancelled(@NonNull DatabaseError error) {}
    });

}

 

페이지네이션 관련 코드입니다. 

scrollChangeListener가 끝도없이 호출돼서 .. 메세지 추가를 주체할 수 없었던 기억이 납니다..

 

맨 위 메세지의 키 값을 잘 저장하고, 

모든 메세지를 불러왔다면, 더는 추가하지 않는 것이

중요한것 같습니다. 

 

화이팅입니다!

 

설명이 부족하다고 느껴지신다면 댓글 남겨주세요.