간단한 채팅 앱 만들기 #3 - RecyclerView 및 Adapter

이전 뉴스 앱 내용 참고
간단한 뉴스 앱 만들기 #2 - RecyclerView

 

간단한 뉴스 앱 만들기 #2 - RecyclerView

간단한 뉴스 앱 만들기 #2 - RecyclerView Android Developer에서 제공하는RecyclerView guide를 참고하면 Adapter가 필요하다 한다. 즉, RecyclerView에 Adpater라는 요소를 만들어 삽입해야 한다는 의미이다. Re..

l-zzu-h.tistory.com

 

채팅 데이터 요소 하나에 들어가는 내용은 이름과 메세지 내용이다.
이를 객체화 해서 보내는 것이 효율적이겠다.
따라서, ChatData클래스를 만들자.

  • ChatData.class

      package com.example.chatapp;
    
      import java.io.Serializable;
    
      public class ChatData implements Serializable {
          private String msg;
          private String nickname;
    
          public String getMsg() { return msg; }
    
          public void setMsg(String msg) { this.msg = msg; }
    
          public String getNickname() { return nickname; }
    
          public void setNickname(String nickname) { this.nickname = nickname; }
      }
    • 이러한 데이터 객체를 DTO(Data Transfer Object)라고 한다.
    • 이와 관련된 DAO, DTO에 대한 내용은 다음에 정리해서 포스팅 할 것이다.

ChatActivity.java를 통해 메인 화면을 띄울 것이다.
RecyclerView에 필요한 기본 요소들을 가져와서 적어두자.

  • ChatActivity.java

      package com.example.chatapp;
    
      import ...
    
      public class ChatActivity extends AppCompatActivity {
          private RecyclerView recyclerView;
          private RecyclerView.Adapter mAdapter;
          private RecyclerView.LayoutManager layoutManager;
          private List<ChatData> chatList;
    
          private EditText EditText_chat;
          private Button Button_send;
    
          private String nick = "nick1";
    
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_chat);
    
              EditText_chat = findViewById(R.id.EditText_chat);
              Button_send = findViewById(R.id.Button_send);
    
              recyclerView = findViewById(R.id.RecyclerView_view);
    
              recyclerView.setHasFixedSize(true);
    
              layoutManager = new LinearLayoutManager(this);
              recyclerView.setLayoutManager(layoutManager);
    
              chatList = new ArrayList<>();
              mAdapter = new ChatAdapter(chatList, ChatActivity.this, nick);
              recyclerView.setAdapter(mAdapter);
          }
      }
    • 어댑터에 데이터를 저장하고 recyclerview에 올리는데, 이 어댑터 하나하나가 메세지 이다.
    • 해당 메세지가 내 데이터인지 아니면, 받은 데이터인지 구별하기 위해 현재 이름을 어댑터에 같이 저장한다.

다음은 adapter에서 데이터를 받아서 화면에 올려보자.

  • ChatAdapter.java

    • 생성자에서 이름과 데이터 리스트들을 받아서 세팅해준다.

        private List<ChatData> mDataset;
        private String myNickname;
      
        public ChatAdapter(List<ChatData> myDataset, String myNickname) {
            mDataset = myDataset;
            this.myNickname = myNickname;
        }
    • 그리고 화면에 띄울 내용인 이름과 메세지 주소를 받아오자.

      • MyViewHolder.class

        public static class MyViewHolder extends RecyclerView.ViewHolder {
          public TextView TextView_nickname;
          public TextView TextView_msg;
        
          public MyViewHolder(LinearLayout v) {
              super(v);
              TextView_nickname = v.findViewById(R.id.TextView_nickname);
              TextView_msg = v.findViewById(R.id.TextView_msg);
          }
        }
    • 그리고, 받아온 데이터를 통해 화면을 보여준다.

      • onBindViewHolder 메소드

        @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
        
          ChatData chat = mDataset.get(position);
          holder.TextView_nickname.setText(chat.getNickname());
          holder.TextView_msg.setText(chat.getMsg());
        
          if(chat.getNickname().equals(myNickname)){
              holder.TextView_nickname.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
              holder.TextView_msg.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
          }
          else{
              holder.TextView_nickname.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_START);
              holder.TextView_msg.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_START);
          }
        }
      • if문은 같은 이름일 경우 오른쪽에 정렬, 자신이 보낸 데이터가 아닐 경우 왼쪽 정렬을 하기 위함이다.

    • 데이터가 갱신될 때마다 데이터를 추가하는 메소드가 필요하다.

      • addChat 메소드
        public void addChat(ChatData chat){
          mDataset.add((chat));
          notifyItemInserted(mDataset.size()-1); // 갱신용
        }
  • 전체 코드

      package com.example.chatapp;
    
      import ...
    
    public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.MyViewHolder> {
        private List<ChatData> mDataset;
        private String myNickname;
        public static View.OnClickListener onClickListener;
    
        public static class MyViewHolder extends RecyclerView.ViewHolder {
            public TextView TextView_nickname;
            public TextView TextView_msg;
    
            public MyViewHolder(LinearLayout v) {
                super(v);
                TextView_nickname = v.findViewById(R.id.TextView_nickname);
                TextView_msg = v.findViewById(R.id.TextView_msg);
            }
        }
    
        public ChatAdapter(List<ChatData> myDataset, String myNickname) {
            mDataset = myDataset;
            this.myNickname = myNickname;
        }
    
        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent,
                                                        int viewType) {
            LinearLayout v = (LinearLayout) LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.row_chat, parent, false);
            MyViewHolder vh = new MyViewHolder(v);
            return vh;
        }
    
    
        @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
    
            ChatData chat = mDataset.get(position);
            holder.TextView_nickname.setText(chat.getNickname());
            holder.TextView_msg.setText(chat.getMsg());
    
            if(chat.getNickname().equals(myNickname)){
                holder.TextView_nickname.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
                holder.TextView_msg.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
            }
            else{
                holder.TextView_nickname.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_START);
                holder.TextView_msg.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_START);
            }
        }
    
        @Override
        public int getItemCount() {
            return (mDataset == null) ? 0 : mDataset.size();
        }
    
        public void addChat(ChatData chat){
            mDataset.add((chat));
            notifyItemInserted(mDataset.size()-1); // 갱신용
        }
    }
    ```

다시 ChatActivity.java로 돌아와서 버튼 클릭에 대한 내용을 아직 하지 않았다.
버튼이 클릭이 되면, 입력된 문자열이 데이터베이스 상에 올라가야한다.
간단한 채팅 앱 만들기 #1 - Firebase 데이터베이스에 쓰는 방법을 참고하자.
멤버 변수에 private DatabaseReference myRef;를 추가해주고, 우리의 DTO를 저장해주자.
해당 내용에 대한 OnClickListener를 정의해보자.

 Button_send.setOnClickListener(new View.OnClickListener() {
     @Override
     public void onClick(View view) {
         String msg = EditText_chat.getText().toString();
         if(msg != null) {
             ChatData chat = new ChatData();
             chat.setMsg(msg);
             chat.setNickname(nick);
             myRef.push().setValue(chat);
         }
     }
 });

그리고, 데이터베이스의 경로를 초기화해주는 작업이 필요하다.

FirebaseDatabase database = FirebaseDatabase.getInstance();
myRef = database.getReference("message");

여기까지는 데이터가 입력되고 버튼이 클릭 되었을 시 데이터베이스에 저장하는 작업이었다.
이젠 데이터가 갱신되었을 때 우리의 화면에 데이터를 보여주는 작업이 필요하다.
1번 포스팅에서 데이터베이스 읽어오는 작업을 해주는 addValueEventListener정의를 하자.

그전에!!

addValueEventListener는 해당 경로의 전체 내용에 대한 변경 사항을 읽어오고 수신대기를 한다.
하지만 채팅은 데이터가 가장 아래만 추가될 뿐 이전 데이터에 대하여 수정작업이 불필요하다.
따라서, 이 메소드를 이용하여 전체 데이터를 읽어 오는 작업은 불필요하다.

addChildEventListener는 데이터베이스의 특정 노드에 대한 변경 사항을 읽고 수신 대기한다.
우리는 마지막 데이터가 추가되었는지만 확인하면 되기에 이 메소드를 이용하는 것이 더 바람직하다 볼 수 있다.

따라서, 우리는 addChildEventListener를 이용해 데이터를 갱신해보자.

myRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(@NonNull DataSnapshot snapshot, @Nullable String previousChildName) {
        ChatData chat = snapshot.getValue(ChatData.class);
        ((ChatAdapter) mAdapter).addChat(chat);
    }
    @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) {}
});

여기서 snapshot이 무엇인가?

데이터베이스의 현재 상태를 잠깐 고정하고 저장한 것을 의미한다.
변화된 그 순간을 스냅샷처럼 찍어서 해당 데이터를 받아올 수 있다.


해당 데이터를 받아오고 우리가 만든 addChat메소드를 통해 화면을 갱신할 수 있다.

  • 전체 코드

      package com.example.chatapp;
    
      import ...
    
      public class ChatActivity extends AppCompatActivity {
          private RecyclerView recyclerView;
          private RecyclerView.Adapter mAdapter;
          private RecyclerView.LayoutManager layoutManager;
          private List<ChatData> chatList;
    
          private EditText EditText_chat;
          private Button Button_send;
    
          private DatabaseReference myRef;
    
          private String nick = "nick1";
    
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_chat);
    
              EditText_chat = findViewById(R.id.EditText_chat);
              Button_send = findViewById(R.id.Button_send);
    
              Button_send.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View view) {
                      String msg = EditText_chat.getText().toString();
                      if(msg != null) {
                          ChatData chat = new ChatData();
                          chat.setMsg(msg);
                          chat.setNickname(nick);
                          myRef.push().setValue(chat);
                      }
    
                  }
              });
    
              recyclerView = findViewById(R.id.RecyclerView_view);
    
              recyclerView.setHasFixedSize(true);
    
              layoutManager = new LinearLayoutManager(this);
              recyclerView.setLayoutManager(layoutManager);
    
              chatList = new ArrayList<>();
              mAdapter = new ChatAdapter(chatList, nick);
              recyclerView.setAdapter(mAdapter);
    
              FirebaseDatabase database = FirebaseDatabase.getInstance();
              myRef = database.getReference("message");
    
              myRef.addChildEventListener(new ChildEventListener() {
                  @Override
                  public void onChildAdded(@NonNull DataSnapshot snapshot, @Nullable String previousChildName) {
                      ChatData chat = snapshot.getValue(ChatData.class);
                      ((ChatAdapter) mAdapter).addChat(chat);
                  }
    
                  @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) {}
              });
          }
      }
  • 데이터의 흐름을 전체적인 흐름을 이미지로 정리해 보았다.

  • 실행 화면

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기