Firestore query adding same data on document change - Android - android

I'm using this method to load active users:
private void loadActiveUsers() {
usersList.clear();
Query activeUsersQuery = firebaseFirestore.collection("Users").whereEqualTo("active", true);
activeUsersQuery.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots, #Nullable FirebaseFirestoreException e) {
for (DocumentChange documentChange: queryDocumentSnapshots.getDocumentChanges()) {
if (documentChange.getType() == DocumentChange.Type.ADDED) {
String user_id = documentChange.getDocument().getId();
Users users = documentChange.getDocument().toObject(Users.class).withId(user_id);
usersList.add(users);
activeUsersRecyclerAdapter.notifyDataSetChanged();
}
if (documentChange.getType() == DocumentChange.Type.MODIFIED) {
String user_id = documentChange.getDocument().getId();
Users users = documentChange.getDocument().toObject(Users.class).withId(user_id);
usersList.remove(users);
usersList.clear();
usersList.add(users);
activeUsersRecyclerAdapter.notifyDataSetChanged();
}
if (documentChange.getType() == DocumentChange.Type.REMOVED) {
String user_id = documentChange.getDocument().getId();
Users users = documentChange.getDocument().toObject(Users.class).withId(user_id);
usersList.remove(users);
activeUsersRecyclerAdapter.notifyDataSetChanged();
}
}
}
});
}
When a new user becomes active, his name is added to the list but multiple times. When the refresh button is clicked, the list is back to normal. And if he is inactive, the recyclerview is messed up.
This is the adapter:
public class ActiveUsersRecyclerAdapter extends RecyclerView.Adapter<ActiveUsersRecyclerAdapter.ViewHolder> {
public List<Users> usersList;
public Context context;
public ActiveUsersRecyclerAdapter(Context context, List<Users> usersList) {
this.context = context;
this.usersList = usersList;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int i) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.user_list_item, parent, false);
context = parent.getContext();
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
final String userID = usersList.get(position).UserID;
String image = usersList.get(position).getImage();
holder.setImage(image);
String username = usersList.get(position).getUsername();
holder.setUsername(username);
holder.view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Toast.makeText(context, userID, Toast.LENGTH_SHORT).show();
}
});
}
#Override
public int getItemCount() {
return usersList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
public View view;
public CircleImageView imageView;
public TextView usernameView;
public ViewHolder(#NonNull View itemView) {
super(itemView);
view = itemView;
}
public void setImage(String image) {
imageView = view.findViewById(R.id.user_image);
Glide.with(context).load(image).into(imageView);
}
public void setUsername(String username) {
usernameView = view.findViewById(R.id.user_username);
usernameView.setText(username);
}
}
}
I tried the holder.setIsRecyclable(false) but it didn't work.
How can I prevent the data from being added multiple times or prevent the recycler from messing up?

When you call addSnapshotListener you're adding a permanent listener to the data. When you attach the listener it immediately starts loading the data and calls your onEvent, but it also stays actively monitoring the data and calls onEvent again if there's any relevant change (such as when another user becomes active). So if you use addSnapshotListener there's no need to call loadActiveUsers multiple times.
If you only want to load the active users when you call loadActiveUsers, consider using the get() method instead. When you call get() the data is only loaded that once single time. So in that case, you can recall loadActiveUsers whenever the user hits refresh to load the updated data.
In general I'd recommend using addSnapshotListener though, as it makes your UI be responsive to changes in the data (no matter where they originate from). But in that case be sure to only attach the listener once (typically when you create the activity) and remove it when you're done (typically when the activity is stopped, paused or hidden).

Related

How to update a specific Recyclerview Row using live data without refreshing entire list

I am learning android and probably this question might have an answer but cant find a way to solve my problem. Ok I am creating a chat application using websocket and works fine but there is a scenario I need to solve that is when a user is chatting on a selected user I need app to have capability to receive text messages from other friends so as to mark as unread and show last received message(just like telegram and whatsapp does) so far on that I have implemented LiveData to do that which work fine as am able to see logs in my console. My issue is on Observe method how do I update a specific item on my list of users to create a notification tally of new messages. Here is my code for live data. The the other issue am stuck with is how to store large text on room database. Am wondering if there is a specific annotation to specify column size.
public class ChatFragment extends Fragment
{
private RecyclerView recyclerView;
private ChatAdapter adapter;
private NotificationRepository notificationRepository;
private NotificationListModel notificationListModel;
public ChatFragment() {
// Required empty public constructor
}
Map<Long,MyFriendsModel> tutor_map;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view= inflater.inflate(R.layout.chat_layout, container, false);
recyclerView=view.findViewById(R.id.my_recycler);
recyclerView.addItemDecoration(new DividerItemDecoration(getContext(),
DividerItemDecoration.VERTICAL));
adapter=new ChatAdapter(getActivity(),getUsers());
adapter.setTutorOnclickListener(this);
recyclerView.setAdapter(adapter);
set_up_live_data();
return view;
}
}
private void set_up_live_data()
{
/***
I do not know how to update my list of users to create total tallies of unread message and last message on new data from observer
*/
notificationRepository=new NotificationRepository(mActivity);
notificationListModel=ViewModelProviders.of(this).get(NotificationListModel.class);
notificationListModel.getLiveNotification().observe(getViewLifecycleOwner(), new Observer<List<NotificationEntity>>() {
#Override
public void onChanged(#Nullable List<NotificationEntity> itemModels) {//this one is invoked from Websocket onMessageMethod and store them in sqlite
Log.e(TAG, "onChanged: ");// am able to see this on the console
List<MyFriendsModel> msg=new ArrayList<>();
for(NotificationEntity m:itemModels)
{
MyFriendsModel ms=new MyFriendsModel();
ms.setLast_message(m.getLast_message());//need to update particular row with this and
ms.setTotal_unread(m.getTotal_unread());//need to update particular row with this
msg.add(ms);
}
}
});
}
private List<MyFriendsModel> getUsers() {
List<MyFriendsModel> messages;//retrofit calls to fetch list of users
return messages;
}
public class ChatAdapter extends RecyclerView.Adapter{
private Context mContext;
private List<MyFriendsModel> mMessageList;
public ChatAdapter(Context context, List<MyFriendsModel> messageList) {
mContext = context;
mMessageList = messageList;
}
#Override
public int getItemCount() {
return mMessageList.size();
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.chat_item_layout, parent, false);;
return new MyHodlerHolder(view);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
MyFriendsModel message = mMessageList.get(position);
((MyHodlerHolder) holder).bind(message);
}
private class MyHodlerHolder extends RecyclerView.ViewHolder {
TextView tutorName,txt_unread_chats,txt_last_sent_message;
RelativeLayout relativeLayout;
MyHodlerHolder(View itemView) {
super(itemView);
tutorName = itemView.findViewById(R.id.text_tutor_name);
txt_unread_chats=itemView.findViewById(R.id.txt_unread_chats);
relativeLayout=itemView.findViewById(R.id.relativeLayout);
txt_last_sent_message=itemView.findViewById(R.id.txt_last_sent_message);
}
public void bind(final MyFriendsModel message)
{
tutorName.setText(message.getUser_name());
txt_unread_chats.setText();//to be updated on by live data. this is where I am stuck.
//I need to update a specific Item not refreshing the entire list
}
}
}
and here is my entity that I need to store large text
#Entity(tableName = "tbl_chats")
public class ChatsEntity
{
#PrimaryKey(autoGenerate = true)
long id;
private String message;//need to make this column to store large text
private String chat_date;
}
I recommend DiffUtil for this situation.
Create a class named MyDiffUtil.
public class MyDiffUtil extends DiffUtil.Callback {
private List<MyFriendsModel> newList;
private List<MyFriendsModel> oldList;
#Override
public int getOldListSize() {
return oldList.size();
}
#Override
public int getNewListSize() {
return newList.size();
}
#Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return newList.get(newItemPosition).id == oldList.get(oldItemPosition).id;
}
#Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
if (!oldList.get(oldItemPosition).message().equals(newList.get(newItemPosition).message()))
return false;
else if (!oldList.get(oldItemPosition).getUser_name().equals(newList.get(newItemPosition).getUser_name()))
return false;
else
return true;
}
}
Update your adapter class with.
public void populate (List<MyFriendsModel> newMessages) {
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new MyDiffUtil (mMessageList, newMessages));
diffResult.dispatchUpdatesTo(this);
}
Instead of adapter=new ChatAdapter(getActivity(),getUsers());
make the necessary changes and call
adapter.populate(getUsers());
To learn more check the documentation.

Is it possible to get the UID of a post that the user selects from a recycle view?

My app allows user's to upload images to Firebase and then populate's the recycle view with the uploaded image. I stored the image under a root reference that consist of the UID along with the current date and time. I would like to access these specific node but I am unsure as to how. Secondly, I would like to know if it's possible to get the UID along with the timestamp of a post the user selects from a recycle view.
Post
g0lybPOGmWXkM13YEbX8pWbzF7x208-A-201900:44
desc: "me"
uid: "g0lybPOGmWXkM13YEbX8pWbzF7x2"
image: "image url"
name: "some name"
profileimage: "Image"
timestamp: "contains time stamp
timestamp: "08-A-201900:44"
This code retrieves the information from the Post node and populates my RecycleView
//Retrieves information stored inside Post node...
public void fetchUserInfo() {
Toast.makeText(this, "fetch info method called", Toast.LENGTH_SHORT).show();
postRef = FirebaseDatabase.getInstance().getReference().child("Post");
postRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot ds : dataSnapshot.getChildren()) {
value = ds.getValue(Post.class);
postIdArray.add(value.getid());
postList.add(value);
}
adapter = new Adapter(Shop_Activity.this, postList);
recyclerView.setAdapter(adapter);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.i("Error", databaseError.toString());
}
});
}
Adapter class
public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder>{
private Context context;
private ArrayList<Post> userPost;
public Adapter(Context context, ArrayList<Post> userPost){
this.context = context;
this.userPost = userPost;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.shop_layout_design,viewGroup, false));
}
//this is where you set the value for the ui elements or load up data for the views
#Override
public void onBindViewHolder(#NonNull ViewHolder viewHolder, int i) {
viewHolder.desc.setText(userPost.get(i).getdesc());
//viewHolder.id.setText(userPost.get(i).getid());
viewHolder.name.setText(userPost.get(i).getName());
Glide.with(this.context).load(userPost.get(i).getimage()).into(viewHolder.image);
Glide.with(this.context).load(userPost.get(i).getProfileimage()).into(viewHolder.profilePicture);
int postKey = viewHolder.getAdapterPosition();
}
#Override
public int getItemCount() {
return userPost.size();
}
//links up ui elements
class ViewHolder extends RecyclerView.ViewHolder{
private TextView desc;
private TextView id;
private ImageView messageImageView;
private ImageView image;
private CircleImageView profilePicture;
private TextView name;
public ViewHolder(#NonNull View itemView) {
super(itemView);
id = itemView.findViewById(R.id.post_title);
desc = itemView.findViewById(R.id.post_desc);
image = itemView.findViewById(R.id.post_image);
profilePicture = itemView.findViewById(R.id.profilePicture);
name = itemView.findViewById(R.id.name);
messageImageView = itemView.findViewById(R.id.messageImageView);
messageImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(context, "Message button pressed", Toast.LENGTH_SHORT).show();
}
});
}
}
}
Assuming that you populate your RecyclerView items from a list of this object, you can get the values of the selected item as follows in the Adapter
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, final int position) {
holder.name.setText(files.get(position).getName());
holder.itemRow.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(TAG, files.get(position).getUid());
}
});
}
Here, files refers to the List/ArrayList of your objects.

Loading images in recycler view with Glide

I am attempting to load images from firebase into a recycler adapter and display them
I have attempted two methods to try and solve this problem the first block of code resulted in the error"You cannot start a load on a not yet attached View or a Fragment where getActivity() returns null (which usually occurs when getActivity() is called before the Fragment is attached or after the Fragment is destroyed)"
The second method the application ran but no images were loaded from reading other similar issues I have seen the isAdded() check but I couldn't seem to get that to work the images are being uploaded into a folder in firebase called post_images and the rest of the data associated with the post (Instagram like app with posting) are in firestore
public BlogRecyclerAdapter(List<BlogPost> blog_list) {
this.blog_list = blog_list;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.blog_list_item, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
String desc_data = blog_list.get(position).getDesc();
holder.setDescText(desc_data);
String image_url = blog_list.get(position).getImage_url();
holder.setBlogImage(image_url);
}
#Override
public int getItemCount() {
return blog_list.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
private View mView;
private TextView descView;
private ImageView blogImageView;
public Context mContext;
public ViewHolder(View itemView) {
super(itemView);
mView = itemView;
}
public void setDescText(String descText) {
descView = mView.findViewById(R.id.blog_desc);
descView.setText(descText);
}
public void setBlogImage(final String downloadUri) {
blogImageView = mView.findViewById(R.id.Post_image_view);
Glide.with(mContext).load(downloadUri).into(blogImageView);
}}}
Other method attempted
mCurrentUser = FirebaseAuth.getInstance().getCurrentUser();
mImageStorage = FirebaseStorage.getInstance().getReference();
final String current_uid = mCurrentUser.getUid();
mUserDatabase = FirebaseDatabase.getInstance().getReference().child("Users").child(current_uid);
mUserDatabase.keepSynced(true);
mUserDatabase.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
StorageReference filepath = mImageStorage.child("profile_images").child(current_uid + (".jpeg"));
Log.d("heere", "S");
// This gets the download url async
filepath.getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
#Override
public void onSuccess(Uri uri) {
//The download url
final String downloadUrl = uri.toString();
Log.d("tag", downloadUrl);
if (!downloadUrl.equals("default")) {
Glide.with(mContext).load(downloadUrl).into(blogImageView);
// Glide.with(getApplicationContext()).load(downloadUrl).into(mDisplayImage);
}
}});}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});}}}
I would like the image to be displayed in the recycler view
Change this line
Glide.with(mContext).load(downloadUri).into(blogImageView);
to this instead:
Glide.with(itemView.getContext()).load(downloadUri).into(blogImageView);
All ViewHolder instances have a field itemView that is guaranteed to be non-null, and you can get the context from that.
Your mContext is not initialized
mContext must be a RecyclerView.Adapter global variable and not a ViewHolder variable
mContext must initialize in onCreateViewHolder function this way
this.mContext = parent.getContext();

Listener only adds last image in list to the RecyclerView

I get a Data snapshot of the user's ids. These ids are also the same as the image ids of the matching profile pictures of the users.
I want to populate the RecyclerView with each image that matches the RecyclerView, but what happens is, that it populate the correct user id, but it only populates the last image in the list of the data snapshot instead of all. So I end up with a list of user ids and a single image at the bottom:
ValueEventListener eventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot ds : dataSnapshot.getChildren()) {
imageName = ds.getKey();
user = new User(imageName);
myDataset.add(user);
storageReference.child("profileImages").child(imageName).getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
#Override
public void onSuccess(Uri uri) {
user.setImageUri(uri);
// Got the download URL
mAdapter.notifyDataSetChanged();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception exception) {
// Handle any errors
}
});
}
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
imagesRef.addListenerForSingleValueEvent(eventListener);
This is how it looks:
This is the adapter:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private ArrayList<User> mDataset;
private MyViewHolder myHolder;
private User user;
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public static class MyViewHolder extends RecyclerView.ViewHolder {
public TextView userIdTextView;
public ImageView userProfileImageView;
public View layout;
public MyViewHolder(View v) {
super(v);
layout = v;
userProfileImageView = (ImageView) v.findViewById(R.id.profile_image);
public TextView userIdTextView = (TextView) v.findViewById(R.id.user_id_text_view);
}
}
// Provide a suitable constructor (depends on the kind of dataset)
public MyAdapter(ArrayList<User> myDataset) {
mDataset = myDataset;
}
// Create new views (invoked by the layout manager)
#Override
public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recycler_view, parent, false);
MyViewHolder vh = new MyViewHolder(v);
return vh;
}
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
myHolder = holder;
user = mDataset.get(position);
Uri userImage = user.getImageUri();
myHolder.userIdTextView.setText(user.getUsername());
Glide.with(myHolder.itemView.getContext() /* context */)
.load(userImage)
.apply(new RequestOptions().override(300, 300))
.into(myHolder.userProfileImageView);
// Return the size of your dataset (invoked by the layout manager)
#Override
public int getItemCount() {
return mDataset.size();
}
}
database structure:
database
|_____users
|___uid1
|___uid2
|___uid3
Your "user" variable cannot be used in that way inside "onSuccess()" callback method. That variable refers always to the LAST "new User()", so when the "download" procedure reach the "onSuccess()" the value of "user" value is THE LATEST executed "new User()". You need to pass something like an "userID" to the download method and when the "onSuccess()" is executed you have to find the right User using that ID, and then set its image.
As getDownloadUrl is async method other user objects are changed and only last object is referenced when onSuccess() is called so last objects uri is set.
Use HashMap<String,User> userMap = new HashMap<String,User>();
Before getDownloadUrl
userMap.put(imageName,user);
in onSuccess()
User user = userMap.get(imageName);
user.setImageUri(uri);
Make imageName final.

how to save the button state recyclerview

Good days, I am writing an android program for online coupon service. I am currently creating a page that allows user to bookmark the item. Then after that they can see it in their bookmarks folder. The problem I am encountering is after the bookmark is clicked and selected for the specific item. but when i scroll up the selected bookmark becomes deselected state. How can I prevent that from happening. Below is my code
Coupon.java
public class Coupon {
private String company_name;
private String offer_desc;
public Coupon() {
}
public Coupon(String company_name, String offer_desc) {
this.company_name = company_name;
this.offer_desc = offer_desc;
}
public String getCompany_name() {
return company_name;
}
public void setCompany_name(String company_name) {
this.company_name = company_name;
}
public String getOffer_desc() {
return offer_desc;
}
public void setOffer_desc(String offer_desc) {
this.offer_desc = offer_desc;
}
}
CouponViewHolder.java
public class CouponViewHolder extends RecyclerView.ViewHolder{
protected TextView company_name;
protected TextView offer_desc;
protected LikeButton star_button;
protected LikeButton heart_button;
public CouponViewHolder(final View item){
super(item);
company_name = (TextView) item.findViewById(R.id.company_name);
offer_desc = (TextView) item.findViewById(R.id.offer_desc);
star_button = (LikeButton) item.findViewById(R.id.star_button);
heart_button = (LikeButton) item.findViewById(R.id.heart_button);
}
}
CouponAdapter.java
public class CouponAdapter extends RecyclerView.Adapter<CouponViewHolder>{
private List<Coupon> couponList;
public CouponAdapter(List<Coupon> couponList) {
this.couponList = couponList;
}
#Override
public CouponViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.fragment_one_card, parent, false);
CouponViewHolder vh = new CouponViewHolder(v);
return vh;
}
#Override
public long getItemId(int position) {
return super.getItemId(position);
}
#Override
public void onBindViewHolder(CouponViewHolder holder, int position) {
Coupon coupon = couponList.get(position);
holder.company_name.setText(coupon.getCompany_name());
holder.offer_desc.setText(coupon.getOffer_desc());
holder.heart_button.setLiked(false);
holder.star_button.setLiked(false);
holder.heart_button.setOnLikeListener(new OnLikeListener() {
#Override
public void liked(LikeButton likeButton) {
notifyDataSetChanged();
}
#Override
public void unLiked(LikeButton likeButton) {
notifyDataSetChanged();
}
});
}
#Override
public int getItemCount() {
return couponList.size();
}
}
Thank you
You have to save the selected things on database or sharedpreferences or whatever saving method you use. you can create an interface for making this action on your class or activity.
1-For the most cases saving the state of the button in the model list is enough. However,when an user change its fragment or activity, there is possibility of that you model list could be destroyed by the system because of memory usage. For this case you have choose whether persist your data using sharedpref,file,db etc or keeping your data in a store whose lifespan is longer than the activity or fragment. It seems that the page you share is related to your app's main functionality so you should keep your data in a repository whose scope is dependent on the app scope. I recommend you to look at mvvm architecture and repository pattern.

Categories

Resources