I keep failed to retrieve the data from subcollection "Diary" when trying on click on a RecyclerView. What I want is when I on click on a RecyclerView, it will display that data stored in the "Diary". What's the problem with my codes?
RecyclerView Java codes:
private void setUpRecyclerView() {
fStore = FirebaseFirestore.getInstance();
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
Query query = fStore.collection("Users").document(user.getUid()).collection("Diary").orderBy("Date", Query.Direction.ASCENDING);
FirestoreRecyclerOptions<ModelClass> options = new FirestoreRecyclerOptions.Builder<ModelClass>()
.setQuery(query, ModelClass.class)
.build();
adapters = new CustomAdapter(options,this);
adapters.startListening();
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapters);
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0,
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, #NonNull RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int direction) {
adapters.deleteItem(viewHolder.getAdapterPosition());
}
}).attachToRecyclerView(recyclerView);
}
#Override
public void onItemClick(final DocumentSnapshot snapshot, int position) {
final ModelClass diary = snapshot.toObject(ModelClass.class);
String id = snapshot.getId();
startActivity(new Intent(diary_user.this,onClickRecyclerViewDiary_user.class));
Toast.makeText(diary_user.this,"Position: " + position + "ID: " + id,Toast.LENGTH_SHORT).show();
}
Stored data Java codes:
check.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FirebaseUser user = fAuth.getCurrentUser();
String uid = user.getUid();
String id = fStore.collection("Users").document(user.getUid()).collection("Diary").document().getId();
DocumentReference df = fStore.collection("Users").document(user.getUid()).collection("Diary").document(id);
Map<String, Object> diaryInfo = new HashMap<>();
diaryInfo.put("Symptom", symptom.getEditText().getText().toString());
diaryInfo.put("Note", note.getEditText().getText().toString());
diaryInfo.put("Date", dateButton.getText().toString());
diaryInfo.put("ID",id);
SimpleDateFormat tf = new SimpleDateFormat("hh:mm a");
String currentTime = tf.format(Calendar.getInstance().getTime());
time.setText(currentTime);
diaryInfo.put("Time", time.getText().toString());
feeling = spinner.getSelectedItem().toString();
diaryInfo.put("Feeling", feeling);
df.set(diaryInfo).addOnSuccessListener(new OnSuccessListener() {
#Override
public void onSuccess(Object o) {
Toast.makeText(add_diary_user.this, "Data successfully stored", Toast.LENGTH_SHORT).show();
startActivity(new Intent(add_diary_user.this, diary_user.class));
finish();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(add_diary_user.this, e.toString(), Toast.LENGTH_SHORT).show();
}
});
}
});
Retrieve data Java codes:
private void getDiary() {
fStore = FirebaseFirestore.getInstance();
FirebaseUser user = fAuth.getCurrentUser();
String id = fStore.collection("Users").document(user.getUid()).collection("Diary").document().getId();
fStore.collection("Users").document(user.getUid()).collection("Diary");
diary.whereEqualTo("ID", id)
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (DocumentSnapshot document : task.getResult()) {
// Do something with your retrieved documents
dateButton.setText((CharSequence) document.getString("Date"));
note.getEditText().setText((CharSequence) document.getString("Note"));
symptom.getEditText().setText((CharSequence) document.getString("Symptom"));
}
}
}
});
}
Database structure:
Output:
I believe the problem in your code is in this line:
String id = fStore.collection("Users").document(user.getUid()).collection("Diary").document().getId();
Where you are using .document().getId(); but without specifying which document you want, which makes id not have the expected value and because of that you don't get any results in the comparison that uses that value later in your execution. To fix that you would need to have this Id stored somewhere and pass it as a parameter to your getDiary() function, or something similar to that.
Related
I want to delete my document from firebase. But first I need to determine the document id. I tried to get document id:
docId = queryDocumentSnapshots.getDocuments().get(pos).getId();
Then, I just wanted to delete my document. But firebase works async so code doesnt work in 'if' statement. When we first click the button, docId variable is null or it takes the docId which was clicked before till the async code part done.
#Override
public void onBindViewHolder(#NonNull AdvertisementHolder holder, int position) {
imgUrl = publishedAdvertisements.get(position).getImgUrl();
holder.petName.setText(publishedAdvertisements.get(position).getPetName());
holder.petCategory.setText(publishedAdvertisements.get(position).getPetCategory());
Picasso.get().load(publishedAdvertisements.get(position).getImgUrl()).into(holder.petImage);
holder.btnDelete.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
firebaseFirestore = FirebaseFirestore.getInstance();
firebaseFirestore.collection("Pets").get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(#NonNull QuerySnapshot queryDocumentSnapshots) {
if (!queryDocumentSnapshots.isEmpty()) {
System.out.println("bos döndü");
docId = queryDocumentSnapshots.getDocuments().get(pos).getId();
}
}
});
System.out.println(docId);
if (docId != null) {
FirebaseFirestore db = FirebaseFirestore.getInstance();
db.collection("Pets").document(docId)
.delete()
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "DocumentSnapshot successfully deleted!");
publishedAdvertisements.clear();
getPublishedAnimals();
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.w(TAG, "Error deleting document", e);
}
});
}
notifyDataSetChanged();
}
});
You should structure your code so that any logic that depends on your asynchronous operation is executed or triggered within the response callback.
You can do something like this:
#Override
public void onBindViewHolder(#NonNull AdvertisementHolder holder, int position) {
imgUrl = publishedAdvertisements.get(position).getImgUrl();
holder.petName.setText(publishedAdvertisements.get(position).getPetName());
holder.petCategory.setText(publishedAdvertisements.get(position).getPetCategory());
Picasso.get().load(publishedAdvertisements.get(position).getImgUrl()).into(holder.petImage);
holder.btnDelete.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
firebaseFirestore = FirebaseFirestore.getInstance();
firebaseFirestore.collection("Pets").get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(#NonNull QuerySnapshot queryDocumentSnapshots) {
// The asynchronous operation has successfully completed
// and returned a value to our 'onSuccess()' callback.
if (!queryDocumentSnapshots.isEmpty()) {
System.out.println("bos döndü");
docId = queryDocumentSnapshots.getDocuments().get(pos).getId();
System.out.println(docId);
// We can now use the value of docId.
if (docId != null) {
FirebaseFirestore db = FirebaseFirestore.getInstance();
db.collection("Pets").document(docId)
.delete()
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "DocumentSnapshot successfully deleted!");
publishedAdvertisements.clear();
getPublishedAnimals();
// (1)
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.w(TAG, "Error deleting document", e);
}
});
}
// I'm not sure how your RecyclerView is set up
// but I'm guessing you might want to move this call
// to 'notifyDataSetChanged()' to the section marked (1)
notifyDataSetChanged();
}
}
});
}
});
}
I have a problem on to add a subcollection "Diary" under collection "Users". How to make it so that one user can have several diary?
Here's Java codes:
check.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FirebaseUser user = fAuth.getCurrentUser();
String uid = user.getUid();
DocumentReference df = fStore.collection("Diary").document(user.getUid());
Map<String, Object> diaryInfo = new HashMap<>();
diaryInfo.put("Symptom", symptom.getEditText().getText().toString());
diaryInfo.put("UID", uid);
diaryInfo.put("Note", note.getEditText().getText().toString());
diaryInfo.put("Date", dateButton.getText().toString());
SimpleDateFormat tf = new SimpleDateFormat("hh:mm a");
String currentTime = tf.format(Calendar.getInstance().getTime());
time.setText(currentTime);
diaryInfo.put("Time",time.getText().toString());
feeling = spinner.getSelectedItem().toString();
diaryInfo.put("Feeling", feeling);
df.set(diaryInfo).addOnSuccessListener(new OnSuccessListener() {
#Override
public void onSuccess(Object o) {
Toast.makeText(add_diary_user.this, "Data successfully stored", Toast.LENGTH_SHORT).show();
startActivity(new Intent(add_diary_user.this, diary_user.class));
finish();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(add_diary_user.this, e.toString(), Toast.LENGTH_SHORT).show();
}
});
}
});
Database structure:
You should place your subcollection inside the corresponding document of your parent collection:
// Create a reference to a new 'Diary' document at Users/user_id/Diary/doc_id
// with an auto-generated document id
DocumentReference df = fStore.collection("Users").document(user.getUid()).collection("Diary").document();
When you need to retrieve the diaries, query the individual user's Diary collection:
fStore.collection("Users").document(user.getUid()).collection("Diary")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
// Do something with your retrieved documents
}
}
}
In my app, the homepage activity holds a sectioned recyclerview, in which the value is initiated by querying Firebase. The data retrieval and the recyclerview was working well at first.
Then, I tried to implement addsnapshotlistener to automatically update my activity without needing the user to refresh the page independently.
However, when I run my code, the recyclerview repeated the same sectioned recyclerview twice.
Here is the code for my activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().hide();
setContentView(R.layout.homepage);
mAuth = FirebaseAuth.getInstance();
db = FirebaseFirestore.getInstance();
///storageRef = FirebaseStorage.getInstance().getReference();
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
UserId = FirebaseAuth.getInstance().getCurrentUser().getUid();
MainRecyclerView = findViewById(R.id.MainContainer);
SignoutButton = findViewById(R.id.SignOutbutton);
checkUserType();
SignoutButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
FirebaseAuth.getInstance().signOut();
///mAuth.GoogleSignInApi.signOut(apiClient);
Intent i = new Intent(HomePage.this, Login.class);
startActivity(i);
}
});
///queries data from firebase
initData();
BottomNavigationView bottomNavigationView = findViewById(R.id.bottomNavigationView);
bottomNavigationView.setSelectedItemId(R.id.appointment);
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()){
case R.id.search:
startActivity(new Intent(getApplicationContext(),Search.class));
overridePendingTransition(0,0);
return true;
case R.id.appointment:
return true;
case R.id.profile:
startActivity(new Intent(getApplicationContext(),Profile.class));
overridePendingTransition(0,0);
return true;
}
return false;
}
});
UpdateToken();
}
#Override
protected void onStart() {
super.onStart();
sectionList.clear();
apnmntList.clear();
CollectionReference colref = db.collection("appointmentsColl").document(UserId)
.collection("Date");
///check for updates inside of the collections
HPListener= colref.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot value, #Nullable FirebaseFirestoreException error) {
for (QueryDocumentSnapshot document : value) {
///query all the values again
db.collection("appointmentsColl").document(UserId)
.collection("Date").document(document.getId())
.collection("appointmentsID")
.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
apnmntList = new ArrayList();
for (DocumentSnapshot querysnapshot: task.getResult()){
apnmtDetails details = new apnmtDetails(
querysnapshot.getString("customer name"),
querysnapshot.getString("barberID"),
querysnapshot.getString("shop name"),
querysnapshot.getString("name"),
querysnapshot.getString("type"),
querysnapshot.getString("status"),
querysnapshot.getString("price"),
querysnapshot.getString("time slot"));
apnmntList.add(details);
section = new Section(document.getString("date"),apnmntList);
}
///notify recyclerview
sectionList.add(section);
mainRecyclerAdapter.notifyDataSetChanged();
}
});
}
}
});
}
#Override
protected void onStop() {
super.onStop();
HPListener.remove();
}
private void checkUserType() {
DocumentReference docRef = db.collection("Users").document(UserId);
docRef.addSnapshotListener(new EventListener<DocumentSnapshot>() {
#Override
public void onEvent(#Nullable DocumentSnapshot value, #Nullable FirebaseFirestoreException error) {
if (value.exists()) {
userType = "Users";
} else {
userType = "Barbers";
}
}
});
}
private void initData(){
db.collection("appointmentsColl").document(UserId)
.collection("Date")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
///getting the list of appointments and their details
db.collection("appointmentsColl").document(UserId)
.collection("Date").document(document.getId())
.collection("appointmentsID")
.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
apnmntList = new ArrayList();
for (DocumentSnapshot querysnapshot: task.getResult()){
apnmtDetails details = new apnmtDetails(
querysnapshot.getString("customer name"),
querysnapshot.getString("barberID"),
querysnapshot.getString("shop name"),
querysnapshot.getString("name"),
querysnapshot.getString("type"),
querysnapshot.getString("status"),
querysnapshot.getString("price"),
querysnapshot.getString("time slot"));
///adding appointmnets into an arraylist
apnmntList.add(details);
///saving the value of the section title and the appointments arraylist inside one object
section = new Section(document.getString("date"),apnmntList);
}
////initializing a new array list with the section's objects
sectionList.add(section);
///initializes the main recyclerview
LinearLayoutManager manager = new LinearLayoutManager(HomePage.this);
manager.setReverseLayout(true);
manager.setStackFromEnd(true);
MainRecyclerView.setLayoutManager(manager);
MainRecyclerView.setAdapter(mainRecyclerAdapter);
}
});
}
}else{
Toast.makeText(HomePage.this,"failed",Toast.LENGTH_SHORT).show();
}
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.i("Check", e.toString() );
}
});
}
private void UpdateToken() {
FirebaseUser firebaseUser= FirebaseAuth.getInstance().getCurrentUser();
String refreshToken= FirebaseInstanceId.getInstance().getToken();
Token token= new Token(refreshToken);
FirebaseDatabase.getInstance().getReference("Tokens").child(FirebaseAuth.getInstance().getCurrentUser().getUid()).setValue(token);
}
Here is what my output looks like before and after implementing addsnapshotlistener:
Before
After
I tinkered with the addsnapshotlistener for a while because I was not sure whether it is the cause for the problem, so I tried to replace the method inside the addsnapshotlistener with:
finish();
startActivity(getIntent()
I thought this would work, but when I tried to run my code, the homepage started to refresh itself endlessly without stopping.
I tried to search for people with the same problem as my own, but the posts that I find stated that their addsnapshotlistener triggers correctly but return twice the instance of the data.
The method triggers itself when I open my activity, and it triggers itself even if there are no updates in Firebase.
I would appreciate it if anyone can help me with this problem or maybe guide me to any link or post that can help solve my problem.
You mention that your data retrieval was working well at first. Have you changed anything recently?
From reading your code, I cannot see where you are handling any errors you might encounter. A listener may fail due to a security setting change or an invalid query.
It would help if you implemented an error callback to help you better understand what has happened. For example, you can capture the error message as e and output the error.
if (e != null) {
System.err.println("Listen failed: " + e);
return;
}
Documentation:
db.collection("cities")
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot snapshots,
#Nullable FirestoreException e) {
if (e != null) {
System.err.println("Listen failed: " + e);
return;
}
for (DocumentChange dc : snapshots.getDocumentChanges()) {
if (dc.getType() == Type.ADDED) {
System.out.println("New city: " + dc.getDocument().getData());
}
}
}
});
I am building a chat app and using pagination in conversation screen. First i am loading ten messages the most recent one from firestore and then next 10 and goes on. when first 10 messages are loaded in screen the next 10 messages are loaded below the recent messages but i want to load old messages over new messages here is sample of my code
toolbar =findViewById(R.id.toolbar5);
toolbar.setTitle("");
setSupportActionBar(toolbar);
Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
finish();
}
});
sendBtn = findViewById(R.id.sendBtn);
textMsg = findViewById(R.id.txtMessage);
profileImg=findViewById(R.id.profile_imageChat);
userName = findViewById(R.id.userName);
userState = findViewById(R.id.userState);
refreshMessages = findViewById(R.id.refreshMessages);
mChat = new ArrayList<>();
firestore =FirebaseFirestore.getInstance();
auth =FirebaseAuth.getInstance();
docref = firestore.collection("users").document(getUser());
documentReference = firestore.collection("users").document(Objects.requireNonNull(auth.getCurrentUser()).getUid());
//setting up online and last seen status
docref.addSnapshotListener(new EventListener<DocumentSnapshot>() {
#SuppressLint("SetTextI18n")
#Override
public void onEvent(#Nullable DocumentSnapshot documentSnapshot, #Nullable FirebaseFirestoreException e) {
if(e!=null)
Log.d("Event",e+"");
if(documentSnapshot != null){
if(documentSnapshot.getBoolean("state").equals(true))
userState.setText("Online");
else
userState.setText(documentSnapshot.getString("offlineDate"));
}
else
Log.d("Error","Null");
}
});
refreshMessages.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
recieveMessage(auth.getCurrentUser().getUid(),getUser());
}
});
sendBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String message = textMsg.getText().toString().trim();
String sender=auth.getCurrentUser().getUid();
if (!message.isEmpty()){
//getting Current time
calendar=Calendar.getInstance();
#SuppressLint("SimpleDateFormat") SimpleDateFormat simpleDateFormat = new SimpleDateFormat("hh:mm a");
final String currentTime = simpleDateFormat.format(calendar.getTime());
//getting current everything
Calendar calendar2 = Calendar.getInstance();
#SuppressLint("SimpleDateFormat") SimpleDateFormat simpleDateFormat1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
final String dateSet = simpleDateFormat1.format(calendar2.getTime());
sendMessage(sender,getUser(),message,currentTime,dateSet);
}
else
Toast.makeText(chatScreen.this, "Error", Toast.LENGTH_SHORT).show();
textMsg.setText("");
}
});
constraintLayout=findViewById(R.id.toolbarLayout);
constraintLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(getApplicationContext(), userProfile.class);
intent.putExtra("userid",getUser());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getApplicationContext().startActivity(intent);
}
});
docref.get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
userName.setText(documentSnapshot.getString("Name"));
recieveMessage(auth.getUid(),getUser());
//Do coding For image
storageReference = FirebaseStorage.getInstance().getReference();
final StorageReference profile = storageReference.child("users/"+getUser());
profile.getDownloadUrl().addOnCompleteListener(new OnCompleteListener<Uri>() {
#Override
public void onComplete(#NonNull Task<Uri> task) {
Picasso.get().load(task.getResult()).into(profileImg);
}
});
}
});
//messages recycler view
recyclerView =findViewById(R.id.recyclerSenderView);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
and here is the function i am calling from onCreate
private void recieveMessage(final String sender , final String reciever) {
colref = FirebaseFirestore.getInstance().collection("chats");
Query next;
if(lastVisible == null)
next = colref.orderBy("dateSet", Query.Direction.DESCENDING).limit(TOTAL_MESSAGES_TO_LOAD);
else
next = colref.orderBy("dateSet").limitToLast(TOTAL_MESSAGES_TO_LOAD).endBefore(lastVisible);
next.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots, #Nullable FirebaseFirestoreException e) {
if(e!= null)
Log.d("onEvent", e+"");
if(queryDocumentSnapshots != null){
// mChat.clear();
//Load item first time
for (QueryDocumentSnapshot ds : queryDocumentSnapshots){
lastvisibleDoc.add(queryDocumentSnapshots.getDocuments().toString());
if(ds.getString("Reciever").equals(sender) && ds.getString("Sender").equals(reciever) ||
ds.getString("Reciever").equals(reciever) && ds.getString("Sender").equals(sender)){
mChat.add(new ChatModel(ds.getString("Sender"),ds.getString("Reciever"),ds.getString("Message"),ds.getString("Time")));
}
}
// Get the last visible document
if(queryDocumentSnapshots.size() > 0){
lastVisible = queryDocumentSnapshots.getDocuments().get(0);
Log.d("VisibleDoc",lastVisible+"");
lastvisibleDoc.clear();
Toast.makeText(chatScreen.this, "wrk", Toast.LENGTH_SHORT).show();
}
adapter = new messgaeAdapter(getApplicationContext(),mChat);
recyclerView.setAdapter(adapter);
adapter.notifyDataSetChanged();
recyclerView.scrollToPosition(mChat.size()-1);
refreshMessages.setRefreshing(false);
}
else
Log.d("Document","null");
}
});
}
In that case you'll want to clear the list of messages by removing the comment markings from this line:
// mChat.clear();
This will remove the previous messages from the list, so that you only see the next page of messages.
I also recommend looking into this code:
adapter = new messageAdapter(getApplicationContext(),mChat);
recyclerView.setAdapter(adapter);
These is no need to create a new adapter when loading the new messages. More idiomatic is to have these two lines in the onCreate so that they happen only once. When you then modify the contents of mChat, you'll call adapter.notifyDataSetChanged() (as you already do) to signal that the data has changed, and the adapter will update the recycler view.
I am trying to create a chat. However, I am facing a big issue. When I delete an entry from the chat recyclerView, the entry stays there, but is removed from the firebase db as expected (I mean only removing from db is working). It only disapears when I close the activity and open it again. My recyclerView has a feature of load 10 more entries each time a user scrolls.
I dont understand how can I remove the entry from the recyclerView?
I need to call the function loadMoreMessages()? Refresh the activity?
MessageAdapter.java
public class MessageAdapter extends RecyclerView.Adapter<MessageAdapter.MessageViewHolder>{
public class MessageViewHolder extends RecyclerView.ViewHolder {
public TextView messageText, displayName;
public CircleImageView profileImage;
public ImageView messageImage;
public MessageViewHolder(View itemView) {
super(itemView);
messageText = (TextView) itemView.findViewById(R.id.message_text_layout);
profileImage = (CircleImageView) itemView.findViewById(R.id.message_profile_layout);
displayName = (TextView) itemView.findViewById(R.id.name_text_layout);
messageImage = (ImageView) itemView.findViewById(R.id.message_image_layout);
}
}
private List<Messages> mMessageList;
private FirebaseAuth mAuth;
private DatabaseReference mUserDatabase;
private DatabaseReference mUserDatabaseSettings;
private String rul, fromUser_t;
private UserAccountSettings mUserAccountSettings;
public MessageAdapter(List<Messages> mMessageList) {
this.mMessageList = mMessageList;
}
#Override
public MessageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.message_single_layout,parent,false);
mAuth = FirebaseAuth.getInstance();
return new MessageViewHolder(v);
}
#Override
public void onBindViewHolder(final MessageViewHolder holder, final int position) {
final String current_user_id = mAuth.getCurrentUser().getUid();
final Messages c = mMessageList.get(position);
final String from_user = c.getFrom();
final String message_type = c.getType();
final String messageContent = c.getMessage();
/**
* popup for text message
*/
// check if the message is from the current user
if(from_user.equals(current_user_id))
{
holder.messageText.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//popup menu to select option
CharSequence options[] = new CharSequence[]
{
"Delete from my phone",
"Delete for Everyone" ,
"Cancel"
};
final AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
builder.setTitle("Message Options");
builder.setItems(options, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
//Click Event for each item.
if(i == 0)
{
deleteSentMessage(position, holder);
// Intent intent = new Intent(holder.itemView.getContext(), ChatActivity.class);
// holder.itemView.getContext().startActivity(intent);
//MessageViewHolder.class.notify();
holder.messageText.setPaintFlags(holder.messageText.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
}
else if(i == 1)
{
deleteMessageForEveryOne(position, holder);
// Intent intent = new Intent(holder.itemView.getContext(), ChatActivity.class);
// holder.itemView.getContext().startActivity(intent);
//notifyDataSetChanged();
//holder.messageText.setVisibility(View.GONE);
}
}
});
builder.show();
}
});
}else
{
// similar code to check if the message is from the other user
}
holder.messageText.setVisibility(View.GONE);
holder.messageImage.setVisibility(View.GONE);
if("text".equals(message_type))
{
holder.messageText.setVisibility(View.VISIBLE);
if(from_user.equals(current_user_id))
{
holder.messageText.setBackgroundResource(R.drawable.message_text_background);
holder.messageText.setTextColor(Color.WHITE);
//todo left right not working
holder.messageText.setGravity(Gravity.LEFT);
}else
{
holder.messageText.setBackgroundColor(Color.WHITE);
holder.messageText.setTextColor(Color.BLACK);
holder.messageText.setGravity(Gravity.RIGHT);
}
holder.messageText.setText(c.getMessage());
}else
{
holder.messageImage.setVisibility(View.VISIBLE);
UniversalImageLoader.setImage(messageContent,holder.messageImage,null,"");
}
}
#Override
public int getItemCount() {
return mMessageList.size();
}
/**
function to Delete from my phone
**/
private void deleteSentMessage(final int position, final MessageViewHolder holder)
{
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
rootRef.child("messages")
.child(mMessageList.get(position).getFrom())
.child(mMessageList.get(position).getTo())
.child(mMessageList.get(position).getMessageID())
.removeValue().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task)
{
if(task.isSuccessful())
{
Toast.makeText(holder.itemView.getContext(), "Deleted Successfully.", Toast.LENGTH_SHORT).show();
}else
{
Toast.makeText(holder.itemView.getContext(), "Error Occurred.", Toast.LENGTH_SHORT).show();
}
}
});
}
/**
function to Delete from receiver phone
**/
private void deleteReceiveMessage(final int position, final MessageViewHolder holder)
{
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
rootRef.child("messages")
.child(mMessageList.get(position).getTo())
.child(mMessageList.get(position).getFrom())
.child(mMessageList.get(position).getMessageID())
.removeValue().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task)
{
if(task.isSuccessful())
{
Toast.makeText(holder.itemView.getContext(), "Deleted Successfully.", Toast.LENGTH_SHORT).show();
}else
{
Toast.makeText(holder.itemView.getContext(), "Error Occurred.", Toast.LENGTH_SHORT).show();
}
}
});
}
/**
function to Delete from both sides
**/
private void deleteMessageForEveryOne(final int position, final MessageViewHolder holder)
{
final DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
rootRef.child("messages")
.child(mMessageList.get(position).getTo())
.child(mMessageList.get(position).getFrom())
.child(mMessageList.get(position).getMessageID())
.removeValue().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task)
{
if(task.isSuccessful())
{
rootRef.child("messages")
.child(mMessageList.get(position).getFrom())
.child(mMessageList.get(position).getTo())
.child(mMessageList.get(position).getMessageID())
.removeValue().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if(task.isSuccessful())
{
Toast.makeText(holder.itemView.getContext(), "Deleted Successfully.", Toast.LENGTH_SHORT).show();
}
}
});
}else
{
Toast.makeText(holder.itemView.getContext(), "Error Occurred.", Toast.LENGTH_SHORT).show();
}
}
});
}
}
ChatActivity.java
public class ChatActivity extends AppCompatActivity {
private static final String TAG = "ChatActivity";
//todo check is user is logedin
//user with whom Iam talking to
private String mChatUser;
private Toolbar mChatToolbar;
private DatabaseReference mRootRef;
private TextView mTitleView, mLastSeenView;
private CircleImageView mProfileImage;
private FirebaseAuth mAuth;
private String mCurrentUserId;
private ImageButton mChatAddBtn;
private ImageButton mChatSendBtn;
private EditText mChatMessageView;
private RecyclerView mMessagesList;
private SwipeRefreshLayout mRefreshLayout;
private final List<Messages> messagesList = new ArrayList<>();
private LinearLayoutManager mLinearLayout;
private MessageAdapter mAdapter;
private static final int TOTAL_ITEMS_TO_LOAD = 10;
private int mCurrentPage = 1;
private int itemPos =0;
private String mLastKey = "";
private String mPrevKey = "";
private static final int GALLERY_PICK = 1;
//Storage Firebase
private StorageReference mImageStorage;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
// create a toolbar and set the title as the user with we are chatting to
mChatToolbar = (Toolbar) findViewById(R.id.chat_app_bar);
setSupportActionBar(mChatToolbar);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
// to add a custom view to the toolbar
actionBar.setDisplayShowCustomEnabled(true);
mRootRef = FirebaseDatabase.getInstance().getReference();
mAuth = FirebaseAuth.getInstance();
mCurrentUserId = mAuth.getCurrentUser().getUid();
mChatUser = getIntent().getStringExtra("user_id");
String userName = getIntent().getStringExtra("user_name");
getSupportActionBar().setTitle(userName);
// String userName = getIntent().getStringExtra("user_name");
LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View action_bar_view = inflater.inflate(R.layout.chat_custom_bar, null);
actionBar.setCustomView(action_bar_view);
// ---------- Custom Action bar items ---
mTitleView = (TextView) findViewById(R.id.custom_bar_title);
mLastSeenView = (TextView) findViewById(R.id.custom_bar_seen);
mProfileImage = (CircleImageView) findViewById(R.id.custom_bar_image);
mChatAddBtn = (ImageButton) findViewById(R.id.chat_add_btn);
mChatSendBtn = (ImageButton) findViewById(R.id.chat_send_btn);
mChatMessageView = (EditText) findViewById(R.id.chat_message_view);
mAdapter = new MessageAdapter(messagesList);
mImageStorage= FirebaseStorage.getInstance().getReference();
mMessagesList = (RecyclerView) findViewById(R.id.messages_list);
mRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.message_swipe_layout);
mLinearLayout = new LinearLayoutManager(this);
mMessagesList.setHasFixedSize(true);
mMessagesList.setLayoutManager(mLinearLayout);
//mMessagesList.setItemViewCacheSize(25);
mMessagesList.setDrawingCacheEnabled(true);
mMessagesList.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
mMessagesList.setAdapter(mAdapter);
loadMessages();
mTitleView.setText(userName);
mRootRef.child("users").child(mChatUser).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
String online = dataSnapshot.child("lastSeen").getValue().toString();
//String image = dataSnapshot.child()
if(online.equals("true")){
mLastSeenView.setText("Online");
}else {
GetTimeAgo getTimeAgo = new GetTimeAgo();
long lastTime = Long.parseLong(online);
String lastSeenTime = getTimeAgo.getTimeAgo(lastTime, getApplicationContext());
mLastSeenView.setText(lastSeenTime);
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
mRootRef.child("Chat").child(mCurrentUserId).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if(!dataSnapshot.hasChild(mChatUser)){
Map chatAddMap = new HashMap();
chatAddMap.put("seen", false);
chatAddMap.put("timestamp", ServerValue.TIMESTAMP);
Map chatUserMap = new HashMap();
chatUserMap.put("Chat/" + mCurrentUserId + "/" + mChatUser, chatAddMap); //add map to current user
chatUserMap.put("Chat/" + mChatUser + "/" + mCurrentUserId, chatAddMap);
mRootRef.updateChildren(chatUserMap, new DatabaseReference.CompletionListener() {
#Override
public void onComplete(#Nullable DatabaseError databaseError, #NonNull DatabaseReference databaseReference) {
if(databaseError != null){
//Log.d(TAG, "onComplete: CHAT LOG", databaseError.getMessage());
Log.e(TAG, "onComplete: CHAT_LOG" + databaseError.getMessage().toString());
}
}
});
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
mChatSendBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
sendMessage();
}
});
// open gallery to get an image
mChatAddBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent galleryIntent = new Intent();
galleryIntent.setType("image/*");
galleryIntent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(galleryIntent,"SELECT IMAGE"), GALLERY_PICK);
}
});
//pagination
mRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
mCurrentPage++;
// each time it loads a new page should go to position zero
itemPos = 0;
loadMoreMessages();
}
});
}
/**
* load more messages everytime uesr refreshes
*/
private void loadMoreMessages() {
DatabaseReference messageRef = mRootRef.child("messages").child(mCurrentUserId).child(mChatUser);
Query messageQuery = messageRef.orderByKey().endAt(mLastKey).limitToLast(10);
messageQuery.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
Messages message = dataSnapshot.getValue(Messages.class);
String messageKey = dataSnapshot.getKey();
if(!mPrevKey.equals(messageKey)){
messagesList.add(itemPos++, message);
Log.d(TAG, "onChildAdded: xx" + mPrevKey + "---" + mLastKey);
} else {
mPrevKey = mLastKey;
}
if(itemPos == 1){
mLastKey = messageKey;
}
Log.d(TAG, "onChildAdded: TOTALKEUS" + "lastkey: " + mLastKey + " | Prev key : " + mPrevKey + " | Message Key : " + messageKey);
mAdapter.notifyDataSetChanged();
mRefreshLayout.setRefreshing(false);
mLinearLayout.scrollToPositionWithOffset(itemPos , 0);
}
#Override
public void onChildChanged(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
}
#Override
public void onChildRemoved(#NonNull DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
/**
* method used to load messages once
*/
private void loadMessages() {
//query to get pagination and last 10 messages
DatabaseReference messageRef = mRootRef.child("messages").child(mCurrentUserId).child(mChatUser);
Query messageQuery = messageRef.limitToLast(mCurrentPage * TOTAL_ITEMS_TO_LOAD);
messageQuery.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
Messages message = dataSnapshot.getValue(Messages.class);
itemPos++;
if(itemPos == 1){
// get the key to be user as a start point when loading more items
String messageKey = dataSnapshot.getKey();
mLastKey = messageKey;
mPrevKey = messageKey;
Log.d(TAG, "onChildAdded: last " +mLastKey + " prev " + mPrevKey + " messa " + messageKey + "-----------");
}
messagesList.add(message);
mAdapter.notifyDataSetChanged();
//pagination - define the bottom of the recycler view
//automatic scroll to bottom
mMessagesList.scrollToPosition(messagesList.size() - 1);
mRefreshLayout.setRefreshing(false);
}
#Override
public void onChildChanged(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
}
#Override
public void onChildRemoved(#NonNull DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
/**
* send photo message
* #param requestCode
* #param resultCode
* #param data
*/
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == GALLERY_PICK && resultCode == RESULT_OK){
Uri imageUri = data.getData();
final String current_user_ref = "messages/" + mCurrentUserId + "/" + mChatUser;
final String chat_user_ref = "messages/" + mChatUser + "/" + mCurrentUserId;
DatabaseReference user_message_push = mRootRef.child("messages")
.child(mCurrentUserId).child(mChatUser).push();
final String push_id = user_message_push.getKey();
StorageReference filepath = mImageStorage.child("message_images").child( push_id + ".jpg");
filepath.putFile(imageUri).addOnCompleteListener(new OnCompleteListener<UploadTask.TaskSnapshot>() {
#Override
public void onComplete(#NonNull Task<UploadTask.TaskSnapshot> task) {
if(task.isSuccessful()){
Task<Uri> result = task.getResult().getMetadata().getReference().getDownloadUrl();
result.addOnSuccessListener(new OnSuccessListener<Uri>() {
#Override
public void onSuccess(Uri uri) {
String download_url = uri.toString();
Map messageMap = new HashMap();
messageMap.put("message", download_url);
messageMap.put("seen", false);
messageMap.put("type", "image");
messageMap.put("time", ServerValue.TIMESTAMP);
messageMap.put("from", mCurrentUserId);
messageMap.put("to", mChatUser);
messageMap.put("messageID", push_id );
Map messageUserMap = new HashMap();
messageUserMap.put(current_user_ref + "/" + push_id, messageMap);
messageUserMap.put(chat_user_ref + "/" + push_id, messageMap);
mChatMessageView.setText("");
mRootRef.updateChildren(messageUserMap, new DatabaseReference.CompletionListener() {
#Override
public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
if(databaseError != null){
Log.d("CHAT_LOG", databaseError.getMessage().toString());
}
}
});
}
});
}
}
});
}
}
}
``
You're using a ChildEventListener in your messageQuery, which has these main methods:
onChildAdded, which is called initially for every child node matching your messageQuery query, and subsequently when any child node is added to the database that falls into messageQuery.
onChildRemoved, which is called when any child node (that falls) is removed from the database (or at least to the part that messageQuery is listening to).
onChildChanged, which is called when any child node that messageQuery is listening is modified in the database.
If I look at your implementation, you've only implemented onChildAdded. So when you remove a child node from the database, Firebase tells your ChildEventListener about it, but that then does nothing.
To remove the message from the UI when it gets removed from the database, you will need to implement:
#Override
public void onChildRemoved(#NonNull DataSnapshot dataSnapshot) {
// TODO: remove the message matching dataSnapshot from messagesList
// TODO: call adapter.notifyDataSetChanged() so that the UI gets updates
}
The list changes size so you have to give new list size to your logic.
Thanks Frank and Vedprakash. It was a quit obvious. At that time I did not understand how to pass the position and remove the item using onChildRemoved. Now I understand. What I did was based on this post: How to use onChildRemoved in Firebase Realtime Database?
#Override
public void onChildRemoved(#NonNull DataSnapshot dataSnapshot) {
Toast.makeText(ChatActivity.this, "Entered on this part!", Toast.LENGTH_SHORT).show();
int index = keyList.indexOf(dataSnapshot.getKey());
messagesList.remove(index);
keyList.remove(index);
//mAdapter.notifyDataSetChanged();
mAdapter.notifyItemRemoved(index);
}
I tested it and is working fine. However, It has a little issue. My chat loads 10 items (defined by: private static final int TOTAL_ITEMS_TO_LOAD = 10;). If the user scrolls it will load more items.
When I open the chat page, the items loaded are from "2" to "11" But, when I delete item, for example, "9" the item "1" show up. Meaning that, the item right before the position zero of the messagesList will show up.
I tried several things. My last try was set the mLinearLayout.scrollToPosition, decrease the TOTAL_ITEMS_TO_LOAD. And the issue stays alive. :(
Before delete
After delete item