이 에러는 **RecyclerView
**와 관련된 문제로 보입니다. **RecyclerView
**에서 발생한 IndexOutOfBoundsException
오류로 추정됩니다.
이러한 오류가 발생하는 일반적인 이유는 데이터 소스(여기서는 FirebaseRecyclerOptions
)와 어댑터의 데이터가 일치하지 않을 때 발생합니다. 이 문제를 해결하기 위해 몇 가지 점검 사항을 확인해야 합니다:
Message
객체를 읽어오는 쿼리가 정확한지 확인하세요. **FirebaseRecyclerOptions
**의 setQuery()
메서드를 사용하여 쿼리를 설정할 때, 쿼리가 적절하게 설정되었는지 다시 한번 확인해보세요.MessageAdapter
클래스의 getItemCount()
메서드가 올바른 개수의 아이템을 반환하는지 확인하세요. 이 메서드는 어댑터의 아이템 개수를 반환해야 합니다.RecyclerView
의 레이아웃 관리자(LinearLayoutManager
)가 적절한 방향과 설정으로 초기화되었는지 확인하세요. 이 경우, 수직 방향(LinearLayoutManager.VERTICAL
)으로 초기화해야 합니다.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.
}
});
}
}
}