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.
Related
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.
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).
I am making an android app where I am using MongoDB and NodeJs as a backend service.I have some posts saved on MongoDb and I am retrieving them in recycler view.I have a button in recycler view when it is clicked I want to fetch an Object Id of an item.
I am successfully fetching all documents in recycler view but the problem is
when I clicked on button in particular item.They are showing Object Id of a document which is inserted recently and not showing correct Object Id of an item.
This is what I have done so far:
MyPostBookAdapter.java
public class MyPostedBookAdapter extends RecyclerView.Adapter<MyPostedBookAdapter.ViewHolder> {
List<PostedModel> listItem;
Activity context;
String id;
public MyPostedBookAdapter(List<PostedModel> listItem, Activity context){
this.listItem = listItem;
this.context = context;
}
#NonNull
#Override
public MyPostedBookAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.posted_book,viewGroup,false);
return new ViewHolder(v);
}
#Override
public void onBindViewHolder(#NonNull final MyPostedBookAdapter.ViewHolder viewHolder, final int i) {
final PostedModel model = listItem.get(i);
//Object Id of post
id = model.getPostId();
viewHolder.userBookName.setText(model.getPurchaseBookName());
RequestOptions requestOptions = new RequestOptions();
requestOptions.placeholder(R.drawable.openbook);
Glide.with(context).load(model.getPurchaseImage()).apply(requestOptions).into(viewHolder.userPostBook);
viewHolder.delete.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(context,id,Toast.LENGTH_SHORT).show();
}
});
}
#Override
public int getItemCount() {
return listItem.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ImageView userPostBook;
TextView userBookName;
Button delete;
public ViewHolder(#NonNull View itemView) {
super(itemView);
userPostBook = (itemView).findViewById(R.id.userPostBook);
userBookName = (itemView).findViewById(R.id.userBookName);
delete = (itemView).findViewById(R.id.delete);
}
}
}
PostedModel.java
public class PostedModel {
String purchaseImage,purchaseBookName,postId;
public PostedModel(){
}
public PostedModel(String purchaseImage, String purchaseBookName,String postId){
this.purchaseBookName = purchaseBookName;
this.purchaseImage = purchaseImage;
this.postId = postId;
}
public String getPurchaseImage() {
return purchaseImage;
}
public void setPurchaseImage(String purchaseImage) {
this.purchaseImage = purchaseImage;
}
public String getPurchaseBookName() {
return purchaseBookName;
}
public void setPurchaseBookName(String purchaseBookName) {
this.purchaseBookName = purchaseBookName;
}
public String getPostId() {
return postId;
}
public void setPostId(String postId) {
this.postId = postId;
}
}
Please let me know how can I get ObjectId correspond to right item.
Any help would be appreciated.
THANKS
The Issue is,
Recycler view load every object given one by one, so variable id had the value of last object. So you need to take id from the selected view.
The Fix is,
#Override
public void onBindViewHolder(#NonNull final MyPostedBookAdapter.ViewHolder viewHolder, final int i) {
final PostedModel model = listItem.get(i);
//Object Id of post
id = model.getPostId();
// You need to set this id to viewHolder.
viewHolder.userBookName.setId(id);
viewHolder.userBookName.setText(model.getPurchaseBookName());
RequestOptions requestOptions = new RequestOptions();
requestOptions.placeholder(R.drawable.openbook);
Glide.with(context).load(model.getPurchaseImage()).apply(requestOptions).into(viewHolder.userPostBook);
viewHolder.delete.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Here you can extract the id which we set at binding
int idOfView = v.userBookName.getId();
Toast.makeText(context,idOfView,Toast.LENGTH_SHORT).show();
}
});
}
Hope this will help you.
Currently i'm experimenting with viewmodels and was wondering if passing a viewmodel to a recyclerview adapter will cause a memory leak? The only purpose of the viewmodel in the adapter is to give a new url of the image to display in the activity
I have no idea if an interface is the better way or is there a better way receive onclick events from a recyclerview?
here is my code:
Viewmodel:
public class WallDetailViewModel extends AndroidViewModel {
private final String apiAddress = "*";
private BlindWall detailWall;
private MutableLiveData<String> mainImageUrl;
public WallDetailViewModel(#NonNull Application application) {
super(application);
}
public LiveData<String> getMainImageUrl() {
if(mainImageUrl == null) {
mainImageUrl = new MutableLiveData<>();
mainImageUrl.postValue(getImageUrl(0));
}
return mainImageUrl;
}
public void setWall(BlindWall wall) {
detailWall = wall;
}
public String getImageUrl(int position) {
String returnValue = null;
if(position >= 0 && position < detailWall.getImagesUrls().size()) {
returnValue = apiAddress + detailWall.getImagesUrls().get(position);
}
return returnValue;
}
public String getWallName() {
return detailWall.getTitle();
}
public String getDutchDescription() {
return detailWall.getDescriptionDutch();
}
public String getEnglishDescription() {
return detailWall.getDescriptionEnglish();
}
public int getImageUrlSize() {
return detailWall.getImagesUrls().size();
}
public void setMainImage(String url) {
mainImageUrl.postValue(url);
}
}
Adapter:
public class ImageSliderAdapter extends RecyclerView.Adapter<ImageSliderAdapter.ViewHolder2> {
private WallDetailViewModel viewModel;
public ImageSliderAdapter(WallDetailViewModel viewModel) {
this.viewModel = viewModel;
}
public static class ViewHolder2 extends RecyclerView.ViewHolder {
public ImageView WallFrontPaper;
public ViewHolder2(View itemView) {
super(itemView);
WallFrontPaper = itemView.findViewById(R.id.ImageItem);
}
}
#Override
public ViewHolder2 onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View v = inflater.inflate(R.layout.imageslider_item, parent, false);
return new ViewHolder2(v);
}
#Override
public void onBindViewHolder(ViewHolder2 holder, int position) {
Picasso.with(holder.itemView.getContext()).load(viewModel.getImageUrl(position)).fit().centerCrop().into(holder.WallFrontPaper);
holder.WallFrontPaper.setOnClickListener(view -> viewModel.setMainImage(viewModel.getImageUrl(position)));
}
#Override
public int getItemCount() {
return viewModel.getImageUrlSize();
}
}
Thank you,
Ian
From the code you posted, I'd say you should be fine. However, having an interface, as you mentioned, could help in making things clearer, as you'd pass that to your adapter instead, without exposing the whole view model.
Looking at this bit
.setOnClickListener(view -> viewModel.setMainImage(viewModel.getImageUrl(position))); makes me wonder if you can't just pass let the view model know that an item has been clicked, since you're using the view model again to figure out which image url to pass as an argument, based on the position.
So, if using an interface you'd have something like setOnClickListener(view -> itemClickListener.itemClicked(position). Your view model would implement this interface.
In my app I have a Fragment representing a list of items.
This is done using a RecyclerViewAdapter. I have implemented an OnListFragmentInteractionListener and therefore if I click an item in the list my app does something.
That works correctly. However, I decided I want to also add a button inside that item. So if I click anywhere other than the button but inside that item's borders, it will proceed with functionality as before, but if I click on the button, it will do something else.
My question is, where do I implement this functionality?
The XML file representing an item in the list is just a couple of text views and a FloatingActionButton.
I thought I have to implement this in my adapter, but where exactly? My adapter looks like this:
public class ContractRecyclerViewAdapter extends RecyclerView.Adapter<ContractRecyclerViewAdapter.ViewHolder> {
private final List<Contract> mValues;
private final OnListFragmentInteractionListener mListener;
public ContractRecyclerViewAdapter(List<Contract> items, OnListFragmentInteractionListener listener) {
mValues = items;
mListener = listener;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.fragment_contract_item, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
holder.mItem = mValues.get(position);
holder.mSell.setText(mValues.get(position).getSell());
if (holder.mDescription != null) {
holder.mDescription.setText(mValues.get(position).getDescription());
}
// TODO: Get the price from parameters?
holder.mPrice.setText(((Double) new Random().nextDouble()).toString());
holder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (null != mListener) {
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
mListener.onListFragmentInteraction(holder.mItem);
}
}
});
holder.mBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
}
});
}
#Override
public int getItemCount() {
return mValues.size();
}
public void addContract(Contract contract) {
mValues.add(contract);
}
public class ViewHolder extends RecyclerView.ViewHolder {
public final View mView;
public final TextView mSell;
public final TextView mDescription;
public final TextView mPrice;
public final FloatingActionButton mBtn;
public Contract mItem;
public ViewHolder(View view) {
super(view);
mView = view;
mSell = (TextView) view.findViewById(R.id.company_text);
mDescription = (TextView) view.findViewById(R.id.contract_description);
mPrice = (TextView) view.findViewById(R.id.price_text);
mBtn = (FloatingActionButton) view.findViewById(R.id.buy_button_from_list);
}
#Override
public String toString() {
return super.toString() + " '" + mSell.getText() + ", " +
mDescription.getText() + "'";
}
}
}
The problem is that wherever I implement the onClickListener for my FloatingActionButton I'll need to have access to my SharedPreferences, so I think I will have to implement it in an Activity class (or Fragment class). Is that true? If I go this way then I'll have to implement it in the Fragment whose content is this view, but then how will I know the position of the item selected?
Thanks.
You can create another method inside your interface,
private interface OnListFragmentInteractionListener {
void onListFragmentInteraction(Contract mItem);
void onButtonClicked(Contract mItem); // You may want to edit the arguments of the method
}
Implement the onButtonClicked in your activity and do your stuff there.
Call the method inside your onClick of the Button as follows,
holder.mBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (null != mListener) {
mListener.onButtonClicked(holder.mItem);
}
}
});
how will I know the position of the item selected?
To get the position of item selected you can utilize the getAdapterPosition() method of RecyclerView.ViewHolder's class inside your View.OnClickListener.
holder.btnXyz.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
int position = holder.getAdapterPosition();
Model item = items.get(position);
/** your code **/
}
});