[Android] - Firebase 를 이용한 채팅시스템 구현

CHA·2023년 3월 19일
0

Android



Firebase Chatting

이번 테스트에서는 Firebase 의 Storage 와 Firestore 를 이용하여 간단한 채팅 시스템을 만들어봅시다.

먼저 MainActivity 부터 만들어둡시다. 여기에서는 닉네임과 프로필 사진을 설정하고, 입장 버튼을 누르면 채팅방으로 들어갈 수 있게끔 만들어봅시다.


activity_main.xml 화면 설계

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#A8FFB850"
    tools:context=".MainActivity">

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/civ_profile"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:src="@mipmap/ic_launcher_round"
        android:layout_marginTop="280dp"
        android:layout_centerHorizontal="true"/>
  
    <EditText
        android:id="@+id/et_nickname"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:maxLength="10"
        android:inputType="text"
        android:hint="닉네임"
        android:gravity="center"
        android:layout_below="@id/civ_profile"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="20dp"
        android:padding="8dp"
        android:background="@drawable/bg_edit_nickname"/>

    <Button
        android:id="@+id/btn_start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/black"
        android:text="입장"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:layout_margin="16dp"
        android:backgroundTint="#A8FFFFFF"/>

</RelativeLayout>


MainActivity.java

전체 코드

package com.example.ex90_firebasechat;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.widget.Toast;

import com.bumptech.glide.Glide;
import com.example.ex90_firebasechat.databinding.ActivityMainBinding;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.firestore.CollectionReference;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.storage.FirebaseStorage;
import com.google.firebase.storage.StorageReference;
import com.google.firebase.storage.UploadTask;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class MainActivity extends AppCompatActivity {

    ActivityMainBinding binding;
    Uri profileUri;
    boolean isFirst = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        binding.civProfile.setOnClickListener(view -> clickImage());
        binding.btnStart.setOnClickListener(view -> clickBtn());

        loadData();

        if(G.nickName != null){
            binding.etNickname.setText(G.nickName);
            Glide.with(this).load(G.profileUrl).into(binding.civProfile);
            isFirst = false;
        }
    }

    void loadData(){
        SharedPreferences sharedPreferences = getSharedPreferences("profile",MODE_PRIVATE);
        G.nickName = sharedPreferences.getString("nickName",null);
        G.profileUrl = sharedPreferences.getString("profileUrl",null);
    }
    void clickImage(){
        Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
        launcher.launch(intent);
    }

    ActivityResultLauncher<Intent> launcher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),result -> {
        if(result.getResultCode() == RESULT_CANCELED) return;
        profileUri = result.getData().getData();
        Glide.with(this).load(profileUri).into(binding.civProfile);
    });

    void clickBtn(){
        if(isFirst) saveData();
        else startActivity(new Intent(this, ChattingActivity.class));
    }

    void saveData(){
        if(profileUri == null) {
            Toast.makeText(this, "프로필 사진을 설정하세요", Toast.LENGTH_SHORT).show();
            return;
        }
        G.nickName = binding.etNickname.getText().toString();

        FirebaseStorage storage = FirebaseStorage.getInstance();
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
        String fileName = "IMG_" + format.format(new Date());
        StorageReference imgRef = storage.getReference("profileImage/"+fileName);

        imgRef.putFile(profileUri).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
            @Override
            public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                imgRef.getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
                    @Override
                    public void onSuccess(Uri uri) {
                        G.profileUrl = uri.toString();
                        Toast.makeText(MainActivity.this, G.profileUrl.toString(), Toast.LENGTH_SHORT).show();

                        FirebaseFirestore firestore = FirebaseFirestore.getInstance();
                        CollectionReference profileRef = firestore.collection("profiles");
                        Map<String,Object> profile = new HashMap<>();
                        profile.put("profileUrl",G.profileUrl);
                        profileRef.document(G.nickName).set(profile);

                        SharedPreferences sharedPreferences = getSharedPreferences("profile",MODE_PRIVATE);
                        SharedPreferences.Editor editor = sharedPreferences.edit();
                        editor.putString("nickName",G.nickName);
                        editor.putString("profileUrl",G.profileUrl);

                        editor.commit();

                        Intent intent = new Intent(MainActivity.this, ChattingActivity.class);
                        startActivity(intent);
                        finish();
                    }
                });
            }
        });
    }
}

이미지 선택

void clickImage(){
    Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
    launcher.launch(intent);
}

ActivityResultLauncher<Intent> launcher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),result -> {
    if(result.getResultCode() == RESULT_CANCELED) return;

    profileUri = result.getData().getData();
    Glide.with(this).load(profileUri).into(binding.civProfile);
});

이미지 선택은 여러번 해보았으니 코드만 읽어보고 넘어갑시다.

G 클래스

채팅방 시스템을 한번 생각해봅시다. 우리는 메인 액티비티에서 이미지를 선택하고 닉네임을 정합니다. 그리고 그 닉네임과 사진이 채팅방 화면에서도 그대로 사용되겠죠. 그러면 Intent 를 이용하여 정보를 전달해주어도 되지만 약간 번거롭습니다. 예를 들어 채팅방으로 넘어갈때만 정보가 필요한게 아니라 여러 화면에서 같은 정보가 필요할 수도 있으니까요. 그래서 자바에는 없는 개념인 전역 변수의 개념을 약간의 꼼수처럼 사용하는 방법이 있습니다. 클래스의 static 변수를 사용하는 개념입니다.

public class G {

    public static String nickName;
    public static String profileUrl;
}

위와 같이 G 클래스를 만들어 여러 액티비티에서 사용할 변수들을 static 변수로 선언해주었습니다. 그러면 어떤 액티비에서건 따로 G 클래스의 참조변수를 만들 필요없이 변수들을 사용할 수 있게 됩니다. 마치 R 클래스에서 뷰들의 id 값을 참조하듯 말입니다.

이미지 및 닉네임 저장

void saveData(){
    if(profileUri == null) {
        Toast.makeText(this, "프로필 사진을 설정하세요", Toast.LENGTH_SHORT).show();
        return;
    }

    G.nickName = binding.etNickname.getText().toString();

    FirebaseStorage storage = FirebaseStorage.getInstance();
    SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
    String fileName = "IMG_" + format.format(new Date());
    StorageReference imgRef = storage.getReference("profileImage/"+fileName);

    imgRef.putFile(profileUri).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
        @Override
        public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
            imgRef.getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
                @Override
                public void onSuccess(Uri uri) {
                    G.profileUrl = uri.toString();
                    Toast.makeText(MainActivity.this, G.profileUrl.toString(), Toast.LENGTH_SHORT).show();

                    FirebaseFirestore firestore = FirebaseFirestore.getInstance();
                    CollectionReference profileRef = firestore.collection("profiles");
                    Map<String,Object> profile = new HashMap<>();
                    profile.put("profileUrl",G.profileUrl);
                    profileRef.document(G.nickName).set(profile);

                    SharedPreferences sharedPreferences = getSharedPreferences("profile",MODE_PRIVATE);
                    SharedPreferences.Editor editor = sharedPreferences.edit();
                    editor.putString("nickName",G.nickName);
                    editor.putString("profileUrl",G.profileUrl);
                    editor.commit();

                    Intent intent = new Intent(MainActivity.this, ChattingActivity.class);
                    startActivity(intent);
                    finish();
                }
            });
        }
    });
}
  • if(profileUri == null)
    프로필 이미지를 선택하지 않았다면, return 을 시켜줍시다. 추후에 채팅방 버튼을 누르면 이 메서드가 호출되므로, 프로필 사진을 설정하지 않는다면 채팅방 입장이 불가합니다.

  • G.nickName = binding.etNickname.getText().toString();
    입력받은 닉네임을 G 클래스의 변수에 저장시켜두었습니다.

  • FirebaseStorage storage = FirebaseStorage.getInstance();
    여기서부터는 이미지를 storage 에 업로드 하는 과정입니다. 업로드시에는 현재 날짜를 기반으로 파일의 이름을 정해둡시다. 그리고 저장소 참조객체를 이용해 사진을 storage 에 업로드 시켜주면 됩니다. 그리고 업로드가 성공 한다면, 업로드 된 사진의 Url 주소와 닉네임을 Firestore 에 저장시키는 작업이 필요합니다.

  • imgRef.getDownloadUrl().addOnSuccessListener()
    저장소 참조객체에게 업로드 된 파일의 url 을 불러오는 작업입니다. 작업이 성공한다면 onSuccess() 메서드가 호출되며, 내부에서는 Firestore DB 에 닉네임과 사진 Url 을 저장시켜주는 작업이 필요합니다.

  • FirebaseFirestore firestore = FirebaseFirestore.getInstance();
    Firestore 객체를 만들어둡시다.

  • profileRef.document(G.nickName).set(profile);
    만들어진 컬렉션 객체를 이용하여 G.nickName 이름으로 된 도큐먼트를 하나 만들고, Map 데이터 형식으로 이루어진 프로필 정보를 저장시켜줍시다. 이러면 Map 형식에 닉네임까지 넣을 필요가 없어집니다.

  • SharedPreferences sharedPreferences = getSharedPreferences("profile",MODE_PRIVATE);
    앱을 처음 실행할 때만 닉네임과 사진을 입력받기 위해 앱이 설치된 디바이스에 영구적으로 저장하기 위해 SharedPreferences 를 이용해줍시다.

채팅방 입장

채팅방으로 입장하기 전에 처리해주어야 하는 작업이 있습니다. 일단 닉네임과 사진을 설정한적이 있다면, 저장되어 있는 정보를 이용해 채팅방에 들어가야 하므로 저장된 데이터를 불러오는 작업을 해주어야 합니다.

void loadData(){
    SharedPreferences sharedPreferences = getSharedPreferences("profile",MODE_PRIVATE);
    G.nickName = sharedPreferences.getString("nickName",null);
    G.profileUrl = sharedPreferences.getString("profileUrl",null);
}

만일 저장된 정보가 있다면 사진과 닉네임이 화면이 띄워질 때 설정됩니다.

loadData();

if(G.nickName != null){
    binding.etNickname.setText(G.nickName);
    Glide.with(this).load(G.profileUrl).into(binding.civProfile);
    isFirst = false;
}

isFirst 는 나중에 입장 버튼을 눌렀을 때, 이미지와 닉네임을 저장시킬지 말지 결정해주는 변수입니다.

그러면 이제 채팅방 입장 버튼을 눌러봅시다.

void clickBtn(){
    if(isFirst) saveData();
    else startActivity(new Intent(this, ChattingActivity.class));
}

isFirst 는 초기값이 true 이며 한번 만들어진 닉네임이 있다면 false 로 바뀝니다. 그래서 초기에 한번만 saveDate() 를 실행합니다. 만일 생성된 닉네임이 있다면, 바로 채팅화면으로 넘어가게 됩니다.


activity_chatting.xml 화면 설계

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ChattingActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        android:orientation="vertical"
        android:layout_above="@id/layout"
        android:layout_marginBottom="40dp"
        android:background="#8A8888"
        app:stackFromEnd="true"
        />

    <LinearLayout
        android:id="@+id/layout"
        android:layout_alignParentBottom="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="4dp"
        android:background="#A8FFB850">

        <EditText
            android:id="@+id/et"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="@drawable/bg_edit_nickname"
            android:hint="메세지"
            android:maxLines="3"
            android:inputType="textMultiLine"
            android:padding="8dp"/>

        <Button
            android:id="@+id/btn_send"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_marginLeft="10dp"
            android:backgroundTint="#36DF6038"
            android:layout_gravity="center"
            android:text="SEND"
            />

    </LinearLayout>


</RelativeLayout>

ChattingActivity.java

전체 코드

package com.example.ex90_firebasechat;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;

import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;

import com.example.ex90_firebasechat.databinding.ActivityChattingBinding;
import com.google.firebase.firestore.CollectionReference;
import com.google.firebase.firestore.DocumentChange;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.EventListener;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.FirebaseFirestoreException;
import com.google.firebase.firestore.QuerySnapshot;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Map;


public class ChattingActivity extends AppCompatActivity {
    ActivityChattingBinding binding;
    FirebaseFirestore firestore;
    CollectionReference chatRef;
    ArrayList<MessageItem> messageItems = new ArrayList<>();
    MessageAdapter adapter;

    String chatRoomName = "chat1";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityChattingBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        getSupportActionBar().setTitle(chatRoomName);
        getSupportActionBar().setSubtitle("상대방 이름");

        adapter = new MessageAdapter(this,messageItems);
        binding.recycler.setAdapter(adapter);

        firestore = FirebaseFirestore.getInstance();
        chatRef = firestore.collection(chatRoomName);

        chatRef.addSnapshotListener(new EventListener<QuerySnapshot>() {
            @Override
            public void onEvent(@Nullable QuerySnapshot value, @Nullable FirebaseFirestoreException error) {
                List<DocumentChange> changes = value.getDocumentChanges();
                for(DocumentChange documentChange : changes){
                    DocumentSnapshot snapshot = documentChange.getDocument();

                    Map<String,Object> msg = snapshot.getData();
                    String name = msg.get("name").toString();
                    String message = msg.get("message").toString();
                    String profileUrl = msg.get("profileUrl").toString();
                    String time = msg.get("time").toString();

                    messageItems.add(new MessageItem(name,message,profileUrl,time));
                    adapter.notifyItemInserted(messageItems.size()); binding.recycler.scrollToPosition(adapter.getItemCount()-1);
                }
            }
        });
        binding.btnSend.setOnClickListener(view -> clickSend());
    }
    void clickSend(){
        String nickName = G.nickName;
        String message = binding.et.getText().toString();
        String profileUrl = G.profileUrl;

        Calendar calendar = Calendar.getInstance();
        String time = calendar.get(Calendar.HOUR_OF_DAY) + ":" + calendar.get(Calendar.MINUTE);

        MessageItem item = new MessageItem(nickName,message,profileUrl,time);

        chatRef.document("MSG_" + System.currentTimeMillis()).set(item);
        binding.et.setText("");

        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
    }
}

my_messagebox.xml

우리는 어댑터를 이용하여 채팅 메시지를 채팅창에 뿌려주어야 합니다. 대량의 데이터야 firebase 에서 뽑아오면 되고, 각 채팅 메시지의 시안을 준비해주어야 합니다. 단, 내가 보낸 메시지는 오른쪽에, 상대방이 보낸 메시지는 왼쪽에 배치되어야 하므로, 둘의 시안은 다르게 준비해주어야 합니다.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:tools="http://schemas.android.com/tools"
    android:padding="16dp"
    tools:viewBindingIgnore="true">

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/civ"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@mipmap/ic_launcher_round"
        android:layout_alignParentRight="true"/>

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="nick name"
        android:textColor="@color/black"
        android:layout_toLeftOf="@id/civ"
        android:layout_marginRight="16dp"/>

    <TextView
        android:id="@+id/tv_msg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello message"
        android:layout_alignRight="@id/tv_name"
        android:layout_below="@id/tv_name"
        android:background="@drawable/bg_messagebox"
        android:padding="16dp"
        android:maxWidth="200dp"/>

    <TextView
        android:id="@+id/tv_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="14:54"
        android:layout_toLeftOf="@id/tv_msg"
        android:layout_alignBottom="@id/tv_msg"
        android:layout_marginRight="6dp"
        android:textSize="10dp"/>


</RelativeLayout>

other_messagebox.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:tools="http://schemas.android.com/tools"
    android:padding="16dp"
    tools:viewBindingIgnore="true">

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/civ"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@mipmap/ic_launcher_round"/>

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="nick name"
        android:textColor="@color/black"
        android:layout_toRightOf="@id/civ"
        android:layout_marginLeft="16dp"/>

    <TextView
        android:id="@+id/tv_msg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello message"
        android:layout_alignLeft="@id/tv_name"
        android:layout_below="@id/tv_name"
        android:background="@drawable/bg_messagebox"
        android:padding="16dp"
        android:maxWidth="200dp"/>

    <TextView
        android:id="@+id/tv_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="14:54"
        android:layout_toRightOf="@id/tv_msg"
        android:layout_alignBottom="@id/tv_msg"
        android:layout_marginLeft="6dp"
        android:textSize="10dp"/>


</RelativeLayout>

어댑터와 아이템 객체 구현하기

어댑터를 이용하여 리사이클러뷰에 뿌려주기 위해서는 대량의 데이터와 시안이 필요하죠. 대량의 데이터를 firebase 에서 뽑아와 저장시키기 위한 용도인 아이템 객체부터 만들어둡시다.

단, 주의할 점은 이 아이템 객체의 변수들을 firebase 에서 사용하기 위해서는 반드시 public 으로 지정해주어야 합니다.

public class MessageItem {

    public String name;
    public String message;
    public String profileUrl;
    public String time;

    public MessageItem() {
    }

    public MessageItem(String name, String message, String profileUrl, String time) {
        this.name = name;
        this.message = message;
        this.profileUrl = profileUrl;
        this.time = time;
    }
}

이제 어댑터를 구현해봅시다. 근데 우리가 지금껏 만들었던 어댑터와는 약간의 변형이 들어갑니다. 내 채팅메시지와 상대방의 채팅메시지를 구분해주어야 하기 때문이죠. 리사이클러뷰의 아이템뷰가 경우에 따라 다른 모양으로 보여야 할 때 사용할 수 있는 메소드가 있습니다. 바로 getItemViewType() 입니다. 일단 코드를 봅시다.

public class MessageAdapter extends RecyclerView.Adapter<MessageAdapter.VH> {
    Context context;
    ArrayList<MessageItem> messageItems;
    final int TYPE_MY_MESSAGE = 0;
    final int TYPE_OTHER_MESSAGE = 1;

    public MessageAdapter(Context context, ArrayList<MessageItem> messageItems) {
        this.context = context;
        this.messageItems = messageItems;
    }


    @Override
    public int getItemViewType(int position) {
        if(messageItems.get(position).name.equals(G.nickName)) return TYPE_MY_MESSAGE;
        else return TYPE_OTHER_MESSAGE;
    }

    @NonNull
    @Override
    public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

        View itemView = null;
        if(viewType == TYPE_MY_MESSAGE){
            itemView = LayoutInflater.from(context).inflate(R.layout.my_messagebox,parent,false);
        }else{
            itemView = LayoutInflater.from(context).inflate(R.layout.other_messagebox,parent,false);
        }

        return new VH(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull VH holder, int position) {
        MessageItem item = messageItems.get(position);

        holder.tvName.setText(item.name);
        holder.tvMsg.setText(item.message);
        holder.tvTime.setText(item.time);
        Glide.with(context).load(item.profileUrl).into(holder.civ);
    }

    @Override
    public int getItemCount() {
        return messageItems.size();
    }

    class VH extends RecyclerView.ViewHolder{

        CircleImageView civ;
        TextView tvName;
        TextView tvMsg;
        TextView tvTime;

        public VH(@NonNull View itemView) {
            super(itemView);
            civ = itemView.findViewById(R.id.civ);
            tvName = itemView.findViewById(R.id.tv_name);
            tvMsg = itemView.findViewById(R.id.tv_msg);
            tvTime = itemView.findViewById(R.id.tv_time);
        }
    }
}

다른 부분들은 모두 기존의 어댑터와 동일하니, 새로운 메소드의 사용법만 알아봅시다.

  • if(messageItems.get(position).name.equals(G.nickName))
    if 문의 조건으로는, 현재 뿌려지는 아이템의 이름값, 즉 사용자의 닉네임이 G.nickName 인지를 판단하고 있습니다. 이러면 뿌려지는 채팅 메시지가 내 채팅메시지 인지 상대방의 채팅메시지 인지를 구분할 수 있는것이죠. 그리고 내 채팅 메시지가 맞다면, 리턴값으로 TYPE_MY_MESSAGE 를 리턴합니다. 실제로는 0 값이죠. 이렇게 리턴된 값은 onCreateViewHolder() 메소드의 2번째 파라미터인 viewType 으로 전달됩니다.

  • if(viewType == TYPE_MY_MESSAGE)
    전달된 viewType 이 어떤 값인지를 판단하고 있습니다. 내 메시지라면, 레이아웃 인플레이터에게 전달하는 레이아웃 파일이 my_messagebox.xml 이 될것이고, 아니라면 other_messagebox.xml 이 될것입니다.

이렇게 하면 손쉽게 채팅메시지를 뿌려줄 수 있습니다.

메시지 추가시 반응할 리스너 구현하기

메시지를 보낼때마다 어댑터를 활용하여 리사이클러뷰에 뿌려야 하므로 리스너가 하나 필요합니다.

chatRef.addSnapshotListener(new EventListener<QuerySnapshot>() {
    @Override
    public void onEvent(@Nullable QuerySnapshot value, @Nullable FirebaseFirestoreException error) {
        List<DocumentChange> changes = value.getDocumentChanges();
        for(DocumentChange documentChange : changes){
            DocumentSnapshot snapshot = documentChange.getDocument();

            Map<String,Object> msg = snapshot.getData();
            String name = msg.get("name").toString();
            String message = msg.get("message").toString();
            String profileUrl = msg.get("profileUrl").toString();
            String time = msg.get("time").toString();

            messageItems.add(new MessageItem(name,message,profileUrl,time));

            adapter.notifyItemInserted(messageItems.size());
            binding.recycler.scrollToPosition(adapter.getItemCount()-1);
        }
    }
});

메시지 보내기 버튼

우리는 채팅 메시지를 뿌려줄 때, firebase DB 에서 데이터를 긁어와서 화면에 뿌려줍니다. 그래서 보내기 버튼을 누르면 일단 DB 에 저장시켜주는 작업이 필요하죠.

void clickSend(){
    String nickName = G.nickName;
    String message = binding.et.getText().toString();
    String profileUrl = G.profileUrl;

    Calendar calendar = Calendar.getInstance();
    String time = calendar.get(Calendar.HOUR_OF_DAY) + ":" + calendar.get(Calendar.MINUTE);

    MessageItem item = new MessageItem(nickName,message,profileUrl,time);

    chatRef.document("MSG_" + System.currentTimeMillis()).set(item);
    binding.et.setText("");

    InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);

}
profile
Developer

0개의 댓글