이 에러는 **RecyclerView**와 관련된 문제로 보입니다. **RecyclerView**에서 발생한 IndexOutOfBoundsException 오류로 추정됩니다.

이러한 오류가 발생하는 일반적인 이유는 데이터 소스(여기서는 FirebaseRecyclerOptions)와 어댑터의 데이터가 일치하지 않을 때 발생합니다. 이 문제를 해결하기 위해 몇 가지 점검 사항을 확인해야 합니다:

  1. 데이터베이스에서 Message 객체를 읽어오는 쿼리가 정확한지 확인하세요. **FirebaseRecyclerOptions**의 setQuery() 메서드를 사용하여 쿼리를 설정할 때, 쿼리가 적절하게 설정되었는지 다시 한번 확인해보세요.
  2. MessageAdapter 클래스의 getItemCount() 메서드가 올바른 개수의 아이템을 반환하는지 확인하세요. 이 메서드는 어댑터의 아이템 개수를 반환해야 합니다.
  3. RecyclerView의 레이아웃 관리자(LinearLayoutManager)가 적절한 방향과 설정으로 초기화되었는지 확인하세요. 이 경우, 수직 방향(LinearLayoutManager.VERTICAL)으로 초기화해야 합니다.
  4. RecyclerView의 뷰 홀더(ViewHolder)와 데이터 간의 일관성이 유지되도록 해야 합니다. 데이터 변경이 있는 경우, **FirebaseRecyclerAdapter**의 startListening()stopListening() 메서드를 호출하여 데이터 소스를 갱신하고, **FirebaseRecyclerAdapter**의 notifyDataSetChanged() 메서드를 호출하여 어댑터에 변경 사항을 알려야 합니다.

해결방법

오류는 FirebaseRecyclerViewAdapter 의 버그 때문입니다.

stopListener 후, 다시 startListener 를 호출하면 Adapter 내부의 데이터 컨테이너를 클리어 해야 하는데, 하지 않아서 발생하는 문제 입니다.그래서 FirebaseRecyclerViewAdapter 대신 ListAdapter 를 사용하도록 변경했습니다.차후 오늘 발생한 오류가 다른 곳에서도 발생하면 제가 작성한 코드처럼 변경하시면 됩니다. 수정한 파일은 ChatActivity 와 MessageAdapter 입니다.

기존 코드

public class ChatActivity extends AppCompatActivity {

    private EditText inputMessage;
    private RecyclerView messageRecyclerView;
    private DatabaseReference messageDatabaseReference;
    private MessageAdapter messageAdapter;
    private String username;  // Replace with actual username

    private String chatRoomId;  // The unique ID of the chat room

    private HashSet<String> profanitySet;  // 욕설을 저장할 HashSet

    private ImageButton btnAddMedia;
    private Uri selectedImageUri;
    private static final intRC_IMAGE_PICKER= 123;
    private StorageReference chatImagesStorageReference;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.promotion_chat);

        inputMessage = findViewById(R.id.edit_chat_message);
        ImageButton sendButton = findViewById(R.id.btn_send);
        messageRecyclerView = findViewById(R.id.recycler_chat_messages);
        btnAddMedia = findViewById(R.id.btn_add_media);

        // Get the chat room ID from the intent or bundle
        chatRoomId = getIntent().getStringExtra("chatRoomId");

        // Use the chat room ID in the database reference
        messageDatabaseReference = FirebaseDatabase.getInstance().getReference().child("Messages").child(chatRoomId);
        chatImagesStorageReference = FirebaseStorage.getInstance().getReference().child("chat_images");

        // Initialize profanity set
        this.profanitySet = new HashSet<>();

        try {
            // Get InputStream from the text file
            InputStream inputStream = getResources().openRawResource(R.raw.word);
            // Get a Scanner object for the InputStream
            Scanner scanner = new Scanner(inputStream);
            // Read each line of the file
            while (scanner.hasNextLine()) {
                // Add each line (profanity word) to the set
                profanitySet.add(scanner.nextLine().trim());
            }
            // Close the Scanner
            scanner.close();
        } catch (Exception e) {
            // Handle possible errors.
            e.printStackTrace();
        }

        sendButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                sendMessage(null);
            }
        });

        btnAddMedia.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
                intent.setType("image/*");
                intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
                startActivityForResult(Intent.createChooser(intent, "Choose an image"),RC_IMAGE_PICKER);
            }
        });

        setUpRecyclerView();

    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode ==RC_IMAGE_PICKER&& resultCode ==RESULT_OK) {
            selectedImageUri = data.getData();
            uploadImage();
        }
    }

    private void uploadImage() {
        if (selectedImageUri != null) {
            final StorageReference imageRef = chatImagesStorageReference.child(selectedImageUri.getLastPathSegment());
            UploadTask uploadTask = imageRef.putFile(selectedImageUri);
            uploadTask.addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
                @Override
                public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                    imageRef.getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
                        @Override
                        public void onSuccess(Uri uri) {
                            // Now we can include the image URL in our message
                            sendMessage(uri.toString());
                            // Notify the adapter about dataset change
//                            messageAdapter.notifyDataSetChanged();
                        }
                    });
                }
            });
        }
    }

    private void setUpRecyclerView() {
        FirebaseRecyclerOptions<Message> options =
                new FirebaseRecyclerOptions.Builder<Message>()
                        .setQuery(messageDatabaseReference, Message.class)
                        .build();

        messageAdapter = new MessageAdapter(options);

        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setStackFromEnd(true);
        messageRecyclerView.setLayoutManager(layoutManager);
        messageRecyclerView.setAdapter(messageAdapter);

        // Register the AdapterDataObserver
        messageAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
            @Override
            public void onItemRangeInserted(int positionStart, int itemCount) {
                super.onItemRangeInserted(positionStart, itemCount);

                int messageCount = messageAdapter.getItemCount();
                int lastVisiblePosition = layoutManager.findLastCompletelyVisibleItemPosition();

                // If the recycler view is initially being loaded or the user is at the bottom of the list, scroll to the bottom of the list to show the newly added message.
                if (lastVisiblePosition == -1 ||
                        (positionStart >= (messageCount - 1) && lastVisiblePosition == (positionStart - 1))) {
                    messageRecyclerView.scrollToPosition(positionStart);
                }
            }
        });

        // Scroll to bottom whenever a new message is added
        messageDatabaseReference.addChildEventListener(new ChildEventListener() {
            @Override
            public void onChildAdded(@NonNull DataSnapshot dataSnapshot, String s) { }

            @Override
            public void onChildChanged(@NonNull DataSnapshot dataSnapshot, String s) { }

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

            @Override
            public void onChildMoved(@NonNull DataSnapshot dataSnapshot, String s) { }

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

    }

    private void sendMessage(@Nullable String imageUrl) {
        String rawMessageContent = inputMessage.getText().toString().trim();
        if (!TextUtils.isEmpty(rawMessageContent)) {
            // Filter message content
            String messageContent = rawMessageContent; // 새로운 변수에 원래 메시지 저장
            for (String profanity : profanitySet) {
                messageContent = messageContent.replaceAll(profanity, "***");
            }

            final String finalMessageContent = messageContent; // 최종 메시지를 저장할 final 변수 생성

            String currentUserId = FirebaseAuth.getInstance().getCurrentUser().getUid();
            long timestamp = System.currentTimeMillis();

            // Get reference to the 'users' node
            DatabaseReference userDatabaseReference = FirebaseDatabase.getInstance().getReference().child("users");

            userDatabaseReference.child(currentUserId).addListenerForSingleValueEvent(new ValueEventListener() {
                @Override
                public void onDataChange(DataSnapshot dataSnapshot) {
                    // Get user name from dataSnapshot
                    String currentUserName = dataSnapshot.child("name").getValue(String.class);

                    // Create message
                    Message chatMessage = new Message(
                            messageDatabaseReference.push().getKey(),
                            currentUserId,
                            currentUserName,
                            finalMessageContent,  // 욕설 필터링된 최종 메세지
                            imageUrl,  // Use the imageUrl argument here
                            timestamp);

                    // Save message to 'Messages' node
                    messageDatabaseReference.child(chatMessage.getMessageId()).setValue(chatMessage)
                            .addOnSuccessListener(new OnSuccessListener<Void>() {
                                @Override
                                public void onSuccess(Void aVoid) {
                                    // Get reference to the 'chatrooms' node
                                    DatabaseReference chatRoomRef = FirebaseDatabase.getInstance().getReference("chatrooms").child(chatRoomId);
                                    chatRoomRef.child("lastMessage").setValue(finalMessageContent);  // Update last message in ChatRoom
                                    // Notify the adapter about dataset change
//                                    messageAdapter.notifyDataSetChanged();
                                }
                            });
                }

                @Override
                public void onCancelled(DatabaseError databaseError) {
                    // Handle possible errors.
                }
            });

            inputMessage.setText("");
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (messageAdapter != null) {
            messageAdapter.startListening();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (messageAdapter != null) {
            messageAdapter.stopListening();
        }
    }
}

public class MessageAdapter extends FirebaseRecyclerAdapter<Message, RecyclerView.ViewHolder> {

    private static final intVIEW_TYPE_SENT= 1;
    private static final intVIEW_TYPE_RECEIVED= 2;

    public MessageAdapter(@NonNull FirebaseRecyclerOptions<Message> options) {
        super(options);
    }

    @Override
    public int getItemViewType(int position) {
        Message message = getItem(position);
        String currentUserId = FirebaseAuth.getInstance().getCurrentUser().getUid();

        if (message.getSenderId().equals(currentUserId)) {
            returnVIEW_TYPE_SENT;
        } else {
            returnVIEW_TYPE_RECEIVED;
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view;

        if (viewType ==VIEW_TYPE_SENT) {
            view = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.promotion_chat_sender, parent, false);
            return new SentMessageHolder(view);
        } else if (viewType ==VIEW_TYPE_RECEIVED) {
            view = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.promotion_chat_receiver, parent, false);
            return new ReceivedMessageHolder(view);
        }

        return null;
    }

    @Override
    protected void onBindViewHolder(RecyclerView.ViewHolder holder, int position, Message model) {
        if (holder instanceof SentMessageHolder) {
            ((SentMessageHolder) holder).bind(model);
        } else if (holder instanceof ReceivedMessageHolder) {
            ((ReceivedMessageHolder) holder).bind(model);
        }
    }

    private class SentMessageHolder extends RecyclerView.ViewHolder {
        TextView messageText, timeText;

        SentMessageHolder(View itemView) {
            super(itemView);

            messageText = itemView.findViewById(R.id.text_sender_message);
            timeText = itemView.findViewById(R.id.text_message_time);
        }

        void bind(Message message) {
            messageText.setText(message.getMessageContent());

            DateFormat dateFormat = new SimpleDateFormat("h:mm a", Locale.getDefault());
            dateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Seoul")); //한국 시간 적용
            String formattedDate = dateFormat.format(new Date(message.getTimestamp()));
            timeText.setText(formattedDate);

        }
    }

    public class ReceivedMessageHolder extends RecyclerView.ViewHolder {
        TextView messageText, timeText, nameText;
        ImageView profileImage;

        ReceivedMessageHolder(View itemView) {
            super(itemView);

            messageText = itemView.findViewById(R.id.text_receiver_message);
            timeText = itemView.findViewById(R.id.text_message_time);
            nameText = itemView.findViewById(R.id.text_receiver_name);
            profileImage = itemView.findViewById(R.id.image_receiver);
        }

        void bind(Message message) {
            messageText.setText(message.getMessageContent());

            DateFormat dateFormat = new SimpleDateFormat("h:mm a", Locale.getDefault());
            dateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Seoul")); //한국 시간 적용
            String formattedDate = dateFormat.format(new Date(message.getTimestamp()));
            timeText.setText(formattedDate);

            // Get sender's information from Firebase
            DatabaseReference userRef = FirebaseDatabase.getInstance().getReference().child("users").child(message.getSenderId());
            userRef.addListenerForSingleValueEvent(new ValueEventListener() {
                @Override
                public void onDataChange(DataSnapshot dataSnapshot) {
                    User sender = dataSnapshot.getValue(User.class);
                    if (sender != null) {
                        nameText.setText(sender.getName());

                        // Load the profile image using Picasso or Glide
                        // For now, we'll use a placeholder image
                        profileImage.setImageResource(R.drawable.chat_sender_img);
                    }
                }

                @Override
                public void onCancelled(DatabaseError databaseError) {
                    // Handle possible errors.
                }
            });
        }
    }
}