My Structure:
I am developing a chat app by using firebase realtime database. I am using FirebaseRecyclerAdapter to display messages. But the thing is on scrolling, messages get duplicated and out of order.
Here is my implementation:
public class FirebaseChatActivity extends AppCompatActivity {
private String messageSenderId;
private String messageReceiverId;
private FirebaseRecyclerAdapter<ChatMessage, MessageViewHolder> firebaseRecyclerAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_firebase_chat);
RecyclerView recyclerView = findViewById(R.id.rv_firebase_chat_activity);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
messageSenderId = "eoSU5m7PyucyC9h30JfHhV6S8Av2";
messageReceiverId = "ZOqofCid0XN5ovIAj1mXhRYxdnO2";
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
Query query = rootRef.child("messages").child(messageSenderId).child(messageReceiverId);
FirebaseRecyclerOptions<ChatMessage> firebaseRecyclerOptions = new FirebaseRecyclerOptions.Builder<ChatMessage>()
.setQuery(query, ChatMessage.class)
.build();
firebaseRecyclerAdapter = new FirebaseRecyclerAdapter<ChatMessage, MessageViewHolder>(firebaseRecyclerOptions) {
#Override
protected void onBindViewHolder(#NonNull MessageViewHolder messageViewHolder, int position, #NonNull ChatMessage message) {
if (message.getMessageType().equals("text")) {
messageViewHolder.showMessage.setText(message.getMessageText());
} else if (message.getMessageType().equals("image")) {
try {
Glide.with(getApplicationContext())
.load(message.getPhotoUrl())
.placeholder(R.drawable.no_image2)
.error(R.drawable.image_1)
.into(messageViewHolder.photoImageView);
} catch (Exception e) {
Log.d("MessageAdapterLog", e.toString());
}
}
}
#Override
public MessageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
android.view.View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_item_right, parent, false);
return new MessageViewHolder(view);
}
};
recyclerView.setAdapter(firebaseRecyclerAdapter);
}
private class MessageViewHolder extends RecyclerView.ViewHolder {
public TextView showMessage;
public ImageView photoImageView;
public MessageViewHolder(#NonNull android.view.View itemView) {
super(itemView);
showMessage = itemView.findViewById(R.id.show_message);
photoImageView = itemView.findViewById(R.id.photo_image_view);
}
}
#Override
protected void onStart() {
super.onStart();
firebaseRecyclerAdapter.startListening();
}
#Override
protected void onStop() {
super.onStop();
if (firebaseRecyclerAdapter!= null) {
firebaseRecyclerAdapter.stopListening();
}
}
}
I have tried many different solutions e.g set as mentioned here as well as .addChildEventListener (completely different implementation) but unable to solve the issue.
Full Activity code as well as database structure is attached. Kindly let me know what I am doing wrong.
Thanks
The problem is that everytime you scroll, to make a new item visible again it will run the code inside your onBindViewHolder to inflate that view and rebind it.
if (message.getMessageType().equals("text")) {
messageViewHolder.showMessage.setText(message.getMessageText());
} else if (message.getMessageType().equals("image")) {
try {
Glide.with(getApplicationContext())
.load(message.getPhotoUrl())
.placeholder(R.drawable.no_image2)
.error(R.drawable.image_1)
.into(messageViewHolder.photoImageView);
} catch (Exception e) {
Log.d("MessageAdapterLog", e.toString());
}
}
}
to solve this problem you will need to override two methods inside your adapter
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
Related
i have a document inside firestore, that document also has a list inside is called "foods". I want to be able to load only that list of "foods" into a recyclerview, I'm not sure how to query the list for recycler options. Here is what the DB structure looks like:
Here is my Adapter
List<Order> myOrders;
public OrderDetailAdapter(#NonNull FirestoreRecyclerOptions<Order> options) {
super(options);
}
#Override
protected void onBindViewHolder(#NonNull OrderDetailAdapter.DetailsHolder holder, int position, #NonNull Order model) {
Order order = myOrders.get(position);
holder.product_name.setText(model.getProductName());
holder.product_price.setText(model.getPrice());
holder.product_quantity.setText(model.getQuantity());
holder.product_discount.setText(model.getDiscount());
}
#NonNull
#Override
public OrderDetailAdapter.DetailsHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.order_item_layout, parent,false);
return new DetailsHolder(v);
}
public class DetailsHolder extends RecyclerView.ViewHolder {
TextView product_name, product_price, product_discount, product_quantity;
public DetailsHolder(#NonNull View itemView) {
super(itemView);
product_name = itemView.findViewById(R.id.product_name);
product_price = itemView.findViewById(R.id.product_price);
product_discount = itemView.findViewById(R.id.product_discount);
product_quantity = itemView.findViewById(R.id.product_quantity);
}
}
#Override
public int getItemCount() {
return myOrders.size();
}
}
Here is my Activity:
private FirebaseFirestore db = FirebaseFirestore.getInstance();
DocumentReference documentReference;
TextView order_id, order_comment, order_date, order_total;
String order_id_value ="";
RecyclerView lstFoods;
RecyclerView.LayoutManager layoutManager;
OrderDetailAdapter orderAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_order_details);
if (getIntent() !=null)
order_id_value = getIntent().getStringExtra("order_number");
documentReference = db.collection("Requests").document(order_id_value);
lstFoods = findViewById(R.id.food_list);
lstFoods.setHasFixedSize(true);
layoutManager = new LinearLayoutManager(this);
lstFoods.setLayoutManager(layoutManager);
order_id.setText(order_id_value);
documentReference.addSnapshotListener(new EventListener<DocumentSnapshot>() {
#Override
public void onEvent(#Nullable DocumentSnapshot documentSnapshot, #Nullable FirebaseFirestoreException e) {
if (documentSnapshot.exists()){
Order order = documentSnapshot.toObject(Order.class);
orderAdapter.notifyDataSetChanged();
lstFoods.setAdapter(orderAdapter);
}else{
Log.d("TAG", "ERROR");
}
}
});
}
#Override
protected void onStart() {
super.onStart();
orderAdapter.startListening();
}
#Override
protected void onStop() {
super.onStop();
orderAdapter.stopListening();
}
}
When i try to run it i get the following error:
error: incompatible types: List cannot be converted to FirestoreRecyclerOptions
super(options);
I have a restaurant app with a food page that is populated by 2 Firestore recycler views. The weird this is one restaurant loads everything normal but the others fail. I have gone over the database and everything seems fine there but it just won't work. here are some images showing the differences:
PS how do i resize uploaded images?
As you can see there is no size option in the first pic but the 2nd pic has them.
here is my code:
the page:
private void setUpSizes() {
if (getIntent() != null)
foodId = getIntent().getStringExtra("foodid");
Log.d("TAG", "setUpSizes: "+foodId);
if (!foodId.isEmpty() && foodId != null) {
Log.d("TAG", "setUpSizes: "+foodId);
Query foodSizes = db.collectionGroup("sizes").whereEqualTo("id", foodId);
FirestoreRecyclerOptions<SizeOptionsModel> options = new FirestoreRecyclerOptions.Builder<SizeOptionsModel>()
.setQuery(foodSizes, SizeOptionsModel.class)
.build();
mSelectedFoodSizeAdapter = new SizeOptionsAdapter(options);
RecyclerView recyclerView = findViewById(R.id.size_recycler);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(mSelectedFoodSizeAdapter);
}
}
}
#Override
protected void onStart() {
super.onStart();
mSelectedFoodExtrasAdapter.startListening();
mSelectedFoodSizeAdapter.startListening();
}
#Override
protected void onStop() {
super.onStop();
mSelectedFoodExtrasAdapter.stopListening();
mSelectedFoodSizeAdapter.stopListening();
}
}
the adapter:
private FoodSizesHolder holder;
private int mSelectedItem = -1;
private Context context;
public String getPriceFromAdapter() {
return priceFromAdapter;
}
private String priceFromAdapter;
public SizeOptionsAdapter(#NonNull FirestoreRecyclerOptions<SizeOptionsModel> options) {
super(options);
}
#Override
protected void onBindViewHolder(#NonNull FoodSizesHolder holder, int position, #NonNull SizeOptionsModel model) {
holder.food_size.setText(model.getName());
holder.food_size_price.setText(model.getPrice());
//Allow only one radio button to be checked
holder.food_size.setChecked(position == mSelectedItem);
//Auto select the first item
}
#Override
public int getItemCount() {
return super.getItemCount();
}
#NonNull
#Override
public FoodSizesHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.sizes_item, parent, false);
return new FoodSizesHolder(v);
}
class FoodSizesHolder extends RecyclerView.ViewHolder {
RadioButton food_size;
TextView food_size_price;
public FoodSizesHolder(#NonNull final View itemView) {
super(itemView);
food_size = itemView.findViewById(R.id.sizes_checkbox);
food_size_price = itemView.findViewById(R.id.sizes_prices);
if (food_size.isChecked()){
priceFromAdapter = String.valueOf(food_size_price.getText());
}
}
});
}
}
}
the error message is : incompatible types: FirestoreRecyclerOptions cannot be converted to FirebaseRecyclerOptions ----
and the code is as follows.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_agencies);
firebaseFirestore= FirebaseFirestore.getInstance();
//views definition
agencyFeed = (RecyclerView)findViewById(R.id.agencyRecyclerView);
//------Requirements for retrieving data and storing it to recycler view---
//Query ----- for retrieving data from firestore
Query query = firebaseFirestore.collection("Agencies");
// recyclerOptions
FirestoreRecyclerOptions<AgencyModel> options = new FirestoreRecyclerOptions.Builder<AgencyModel>()
.setQuery(query,AgencyModel.class)
.build();
adapter = new FirebaseRecyclerAdapter<AgencyModel, AgencyViewHolder>(options){
#NonNull
#Override
public AgencyViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.agency_cards,parent,false);
return new AgencyViewHolder(view);
}
#Override
protected void onBindViewHolder(#NonNull AgencyViewHolder holder, int position, #NonNull AgencyModel model) {
holder.name.setText(model.getName());
holder.location.setText(model.getLocation());
}
};
agencyFeed.hasFixedSize();
agencyFeed.setLayoutManager(new GridLayoutManager(this,2));
agencyFeed.setAdapter(adapter);
}
//viewholder class
public class AgencyViewHolder extends RecyclerView.ViewHolder {
private TextView name, location;
public AgencyViewHolder(#NonNull View itemView) {
super(itemView);
name= itemView.findViewById(R.id.agency_name); //name of agency
location= itemView.findViewById(R.id.locationTextView);
}
}
#Override
protected void onStop() {
super.onStop();
adapter.stopListening();
}
#Override
protected void onStart() {
super.onStart();
adapter.startListening();
}
}
You are using Firestore according to your query, therefore you have to use FirestoreRecyclerAdapter and not FirebaseRecyclerAdapter. Therefore change:
adapter = new FirebaseRecyclerAdapter<AgencyModel, AgencyViewHolder>(options){
into this:
adapter = new FirestoreRecyclerAdapter<AgencyModel, AgencyViewHolder>(options){
I use this tutorial to implement DiffUtil in my recyclerview. My aim is to add single item to the bottom of the recyclerview without reloading the rest of the recyclerview. I used firestore addSnapshotListener to call the adapter. The problem is the onBindViewHolder is called multiple times (ie, no of items present in the list). I dont think that is suppose to happen when using DiffUtil, right? It should only call onBindViewHolder for the item that is added to recyclerview, right?
This is the code whre I call the adapter:
#Override
protected void onStart()
{
super.onStart();
reference
.addSnapshotListener((Activity)context, new EventListener<QuerySnapshot>()
{
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots,
#Nullable FirebaseFirestoreException e)
{
if (e != null)
{
Toast.makeText(context, "Error", Toast.LENGTH_SHORT).show();
return;
}
CommentsListAdapter adapter = new CommentsListAdapter(context);
commentsRecyclerView.setAdapter(adapter);
comments = new ArrayList<>();
for (QueryDocumentSnapshot snapshot : queryDocumentSnapshots)
{
Comment comment = snapshot.toObject(Comment.class).withId(snapshot.getId());
comments.add(comment);
}
adapter.submitList(comments);
commentsRecyclerView.smoothScrollToPosition(adapter.getItemCount());
}
});
}
This is the adapter class:
class CommentsListAdapter extends ListAdapter<Comment, CommentsListAdapter.CommentsViewHolder>
{
private Context context;
protected CommentsListAdapter(Context context)
{
super(DIFF_CALLBACK);
this.context = context;
}
private static final DiffUtil.ItemCallback<Comment> DIFF_CALLBACK = new DiffUtil.ItemCallback<Comment>()
{
#Override
public boolean areItemsTheSame(#NonNull Comment oldItem, #NonNull Comment newItem)
{
return oldItem.commentId.equals(newItem.commentId);
}
#Override
public boolean areContentsTheSame(#NonNull Comment oldItem, #NonNull Comment newItem)
{
return oldItem.commentId.equals(newItem.commentId);
}
};
#NonNull
#Override
public CommentsViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType)
{
View itemView = LayoutInflater.from(context)
.inflate(R.layout.comment_list_item, parent, false);
return new CommentsViewHolder(itemView);
}
#Override
public void onBindViewHolder(#NonNull final CommentsViewHolder holder, int position)
{
System.out.println("POSITION: " + position);
holder.commentText.setText(getItem(position).getComment());
holder.timeText.setText(getItem(position).getCommentDateCreated());
}
public class CommentsViewHolder extends RecyclerView.ViewHolder
{
private TextView commentText;
private TextView timeText;
public CommentsViewHolder(#NonNull View itemView)
{
super(itemView);
commentText = itemView.findViewById(R.id.commentText);
timeText = itemView.findViewById(R.id.timeText);
}
}
}
I am new to DiffUtil. So, is it suppose to happen ? Or Is there anything wrong with the code?
Every time when you get callback from Firestore you recreate your CommentsListAdapter
Pull the adapter into the global variable in your Activity and call only adapter.submitList(comments); in Firestore callback
Your Edited code:
CommentsListAdapter adapter = new CommentsListAdapter(context);
#Override
protected void onStart()
{
super.onStart();
commentsRecyclerView.setAdapter(adapter);
reference
.addSnapshotListener((Activity)context, new EventListener<QuerySnapshot>()
{
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots,
#Nullable FirebaseFirestoreException e)
{
if (e != null)
{
Toast.makeText(context, "Error", Toast.LENGTH_SHORT).show();
return;
}
comments = new ArrayList<>();
for (QueryDocumentSnapshot snapshot : queryDocumentSnapshots)
{
Comment comment = snapshot.toObject(Comment.class).withId(snapshot.getId());
comments.add(comment);
}
adapter.submitList(comments);
commentsRecyclerView.smoothScrollToPosition(adapter.getItemCount());
}
});
}
I am developing an app that shows in a RecyclerView an image and a text that comes from firebase.
When I open the application the recyclerview does not appear but if I turn off and then turn on the screen the recyclerview appears correctly with the image and text obtained from firebase. I need help please
ViewHolder:
public class ViewHolderServicio extends RecyclerView.ViewHolder {
View mView;
public ViewHolderServicio(#NonNull View itemView) {
super(itemView);
mView = itemView;
}
public void setDetails(Context ctx, String nombreServicio, String imagenPerfil) {
TextView mNombreServicio = mView.findViewById(R.id.NombreServicio);
ImageView mImagenPerfil = mView.findViewById(R.id.imageViewServicio);
mNombreServicio.setText(nombreServicio);
Picasso.get().load(imagenPerfil).into(mImagenPerfil);
}
}
Model:
public class ModelServicio {
String idUsuario, nombreServicio, descripcion, imagenPerfil;
public ModelServicio(){}
public String getIdUsuario() {
return idUsuario;
}
public void setIdUsuario(String idUsuario) {
this.idUsuario = idUsuario;
}
public String getNombreServicio() {
return nombreServicio;
}
public void setNombreServicio(String nombreServicio) {
this.nombreServicio = nombreServicio;
}
public String getDescripcion() {
return descripcion;
}
public void setDescripcion(String descripcion) {
this.descripcion = descripcion;
}
public String getImagenPerfil() {
return imagenPerfil;
}
public void setImagenPerfil(String imagenPerfil) {
this.imagenPerfil = imagenPerfil;
}
}
class activity:
public class DetalleServicio extends AppCompatActivity {
RecyclerView RVbot;
FirebaseDatabase mFirebaseDatabase;
DatabaseReference mRef;
FirebaseRecyclerAdapter<ModelServicio, ViewHolderServicio> firebaseRecyclerAdapter;
FirebaseRecyclerOptions<ModelServicio> options;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detalle_servicio);
RVbot = findViewById(R.id.RVBot);
RVbot.setHasFixedSize(true);
RVbot.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
mFirebaseDatabase = FirebaseDatabase.getInstance();
mRef = mFirebaseDatabase.getReference("Servicios");
mostrarDatos();
}
private void mostrarDatos() {
options = new FirebaseRecyclerOptions.Builder<ModelServicio>().setQuery(mRef, ModelServicio.class).build();
firebaseRecyclerAdapter = new FirebaseRecyclerAdapter<ModelServicio, ViewHolderServicio>(options) {
#Override
protected void onBindViewHolder(#NonNull ViewHolderServicio holder, int position, #NonNull ModelServicio model) {
holder.setDetails(getApplicationContext(), model.getNombreServicio(), model.getImagenPerfil());
}
#NonNull
#Override
public ViewHolderServicio onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.servicioitem, viewGroup, false);
ViewHolderServicio viewHolder = new ViewHolderServicio(itemView);
return viewHolder;
}
};
RVbot.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
}
#Override
protected void onStart() {
super.onStart();
firebaseRecyclerAdapter.startListening();
RVbot.setAdapter(firebaseRecyclerAdapter);
}
}
Have you tried
firebaseRecyclerAdapter.notifyDataSetChanged();
after calling an Adapter?