Basically we need to get the email from the recyclerview
We tried adding onClickListener on the TextView in the RecyclerView, but when there are more than one entries in the RecyclerView, we cannot get the value.
Is there any way that I can store the values in variables before populating the RecyclerView and passing the values to another Activity?
I tried getting the values from the TextView in the RecyclerView, but I always show the text on the first row
Intent doesn't work on the Adapter class because its not an activity
ArrayList<ModelClass> objModelClassArrayList;
public DatabaseRecyclerAdapter(ArrayList<ModelClass> objModelClassArrayList) {
this.objModelClassArrayList = objModelClassArrayList;
}
#NonNull
#Override
public DatabaseViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType)
{
View singleRow= LayoutInflater.from(parent.getContext()).inflate(R.layout.single_row,parent,false);
return new DatabaseViewHolder(singleRow);
}
#Override
public void onBindViewHolder(#NonNull DatabaseViewHolder holder, int position)
{
ModelClass objModelClass=objModelClassArrayList.get(position);
holder.userNameTV.setText(objModelClass.getName());
holder.userLocation.setText(objModelClass.getAddress());
String e1=objModelClass.getEmail();
holder.userEmail.setText(e1);
}
#Override
public int getItemCount() {
return objModelClassArrayList.size();
}
public static class DatabaseViewHolder extends RecyclerView.ViewHolder
{
TextView userNameTV,userLocation,userEmail;
public DatabaseViewHolder(#NonNull View itemView)
{
super(itemView);
userNameTV=itemView.findViewById(R.id.sr_userNameTV);
userLocation=itemView.findViewById(R.id.sr_location);
userEmail=itemView.findViewById(R.id.sr_email);
}
}
I want to extract the email from a single row and pass it to another page where I can pass it in the DB query to get the results from the database.
first, need to pass context in the constructor of the adapter which can help you to open another activity from the recyclerView adapter. Then Inside BindViewHolder, you can create clickListener like as below,
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String emailText=holder.userEmail.getText().trim();
Intent intent=new Intent(context,activity_name_you _want_ to_open);
intent.setExtra("emailName",emailText);
(name of activity contains RV)context.startActivity(intent)
//Note: here context needs to be typecasted to activity from which we want to open new activity.
}
});
Related
I have a RecyclerView with swiping feature to reveal a delete and edit button.
I added: adapter.notifyItemRemoved(position) and this:
adapter.notifyItemRangeChanged(0, adapter.getItemCount());
when the revealed delete button is clicked, the animation for removing the item works and
The item is deleted from my database
BUT then the deleted item re-appears in my recyclerview. When I change activity and go back, to the activity with the recyclerview, the list that I should be seeing is good.
If I remove the "notifyItemRangeChanged" code, the list updates with the last item repeated.
I think it is my Adapter's getItemCount not properly updating. so what I tried differently was to call my method that generates the list in the first place. This did the trick BUT my remove item animation is gone now because I guess it just skips to re-generate the list....
Any ideas?
Thank you in advanced for your feedback!
****************** UPDATE - ADDING ADAPTER CLASS CODE ****************
public class RVCategoryAdapter extends RecyclerView.Adapter {
Context context;
List categoryItemList;
public RVCategoryAdapter(Context context, List<CategoryItem> categoryItemList) {
this.context = context;
this.categoryItemList = categoryItemList;
}
#NonNull
#Override
public CategoryViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(context).inflate(R.layout.category_item_layout, parent, false);
return new CategoryViewHolder(itemView);
}
#Override
public void onBindViewHolder(#NonNull CategoryViewHolder holder, final int position) {
final int categoryID;
final String categoryTitle;
Glide.with(context).load(categoryItemList.get(position).getImage()).into(holder.ivCategoryIcon);
holder.txtCatID.setText(""+categoryItemList.get(position).getCategoryID());
holder.txtCategoryTitle.setText(categoryItemList.get(position).getTitle());
holder.txtCategoryDesc.setText(categoryItemList.get(position).getDescription());
categoryID = Integer.parseInt(holder.txtCatID.getText().toString());
categoryTitle = holder.txtCategoryTitle.getText().toString();
holder.cardViewItemLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(context, NotesListActivity.class);
intent.putExtra("CategoryID", categoryID);
intent.putExtra("CategoryTitle", categoryTitle);
context.startActivity(intent);
}
});
}
#Override
public int getItemCount() {
return categoryItemList.size();
}
}
In your swipe delete button click listener remove your item from the list, too.
I would suggest you to add delete function in your adapter. Then in that method delete your item from list and call notifyItemRemoved.
public void delete(int position){
categoryItemList.remove(position);
notifyItemRemoved(position);
}
I have a RecyclerView inside a fragment where each line has an adapter which inflates a layout which looks as follows:
I want to access to the value of the EditText (in the following code numberET) of each row and pick the value if EditText is not empty.
How can I cycle on each element of the RecyclerView (I think inside the adapter) to have this behaviour? How can I access the EditText for each element to retrieve the value and use them inside the fragment?
Adapter:
`
public class UserFBEditTextAdapter <T extends UserFBEditTextAdapter.ViewHolder> extends UserFBAdapter<UserFBEditTextAdapter.ViewHolder>{
public UserFBEditTextAdapter(List<UserFB> users,int layoutId, Context context) {
super(users, layoutId, context);
}
#Override
public UserFBEditTextAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
return new UserFBEditTextAdapter.ViewHolder(view);
}
#Override
public void onBindViewHolder(UserFBAdapter.ViewHolder holder, int position) {
holder.userFB = users.get(position);
holder.usernameTV.setText(holder.userFB.getName());
}
public class ViewHolder extends UserFBAdapter.ViewHolder {
protected EditText numberET;
public ViewHolder(View itemView) {
super(itemView);
numberET = (EditText) itemView.findViewById(R.id.number_et);
}
}
}`
Fragment:
public class ExpenseCustomFragment extends Fragment {
private OnFragmentInteractionListener mListener;
private UserFBAdapter adapter;
private RecyclerView userCustomList;
public ExpenseCustomFragment() {
// Required empty public constructor
}
public static ExpenseCustomFragment newInstance() {
ExpenseCustomFragment fragment = new ExpenseCustomFragment();
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_expense_custom, container, false);
userCustomList = (RecyclerView) view.findViewById(R.id.amountlist_rv);
userCustomList.setLayoutManager(new LinearLayoutManager(getContext()));
NewExpenseDescriptionActivity activity = (NewExpenseDescriptionActivity) getActivity();
adapter = new UserFBEditTextAdapter(activity.getUsersGroup(), R.layout.listitem_expensecustom, getContext());
userCustomList.setAdapter(adapter);
return view;
}
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
void onFragmentInteraction(Uri uri);
}
}
You have to retain that data in some map-based data structure, and then, whenever those values are needed, iterate over that data structure.
You cannot rely on saving that data in a ViewHolder, because ViewHolders are being reused as soon as you perform scrolling. If you currently do not save the data that is filled in EditText, then you'll lose that data if you have many items and perform scrolling (i.e. screen fits 10 items, but your adapter is 20 items, as soon as you scroll to 15th item, the EditText value for the first item will be lost).
private Map<Integer, String> map = new ArrayMap<>(adapterSize);
...
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
String text = map.get(holder.getAdapterPosition());
// maybe we haven't yet saved text for this position
holder.editText.setText(text != null ? text : "");
// updated value in map as soon as the `EditText` in this position changes
holder.editText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
map.put(holder.getAdapterPosition(), s.toString());
}
#Override
public void afterTextChanged(Editable s) {
}
});
}
Now you'll have access to all EditText values in your RecyclerView. The only change that you can consider is updating map after user stops typing. Currently if user types "123456789" the map will be updated 9 times, whereas we need only once. An easy solution to this can be using RxJava's debounce operator combined with RxBinding library. This maybe sounds complicated, but you can see how plain it is in this answer.
This will work. But after you perform scrolling up and forth, soon you'll find out that some mess is going on there. That's because each time onBindViewHolder() gets called a new TextWatcher is being added to the EditText that already has a TextWatcher attached to it. Thus, you also have to take care of removing the TextWatcher after your ViewHolder is being recycled.
But there is no an API to remove all TextWatcher of the EditText. You can use a custom EditText implementation shown in this answer which will clear all TextWatcher attached to this EditText:
#Override
public void onViewRecycled(MyViewHolder holder) {
holder.editText.clearTextChangeListeners();
super.onViewRecycled(holder);
}
So I have this app in which I have to make a RecyclerView which contains a list of items that can be deleted/edited e.t.c.
I followed a tutorial on youtube and I made a custom CardView item on another layout and a custom adapter for that item.
Thing is, depending on the state of the item, I have to change something(ex. background color or text color).
When I do that in the RecyclerView activity I get NullPointerException even if I have the id's.
How can I edit those TextViews inside the programmatically generated list of items in the moment I make the Retrofit call?
boolean isEnded;
if(!endedAt.equals("null"))
{
endedAt = endedAt.substring(endedAt.indexOf("T") + 1, endedAt.lastIndexOf(":"));
isEnded=true;
}
else
{
endedAt="ongoing";
isEnded=false;
}
item.timeDifference=createdAt+" - "+endedAt;
if(externalSystem.equals("null"))
{
item.externalSystem="";
}
else
{
item.externalSystem = externalSystem;
}
Log.i("attr",externalSystem);
items.add(item);
itemAdapter=new ItemAdapter(getApplicationContext(), items);
recyclerView.setAdapter(itemAdapter);
if(isEnded) {
error-> externalSystemView.setTextColor(Color.BLACK);
}
The app is rather big, but I think you can get the idea from this piece of code.
Here is the error: Attempt to invoke virtual method 'void android.widget.TextView.setTextColor(int)' on a null object reference
public class ItemAdapter extends
RecyclerView.Adapter<ItemAdapter.ItemViewHolder>{
private Context context;
private ArrayList<Item> itemList;
public ItemAdapter(Context context, ArrayList<Item> itemList)
{
this.context=context;
this.itemList=itemList;
}
#Override
public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater=LayoutInflater.from(parent.getContext());
View view=layoutInflater.inflate(R.layout.item_layout,parent,false);
ItemViewHolder itemViewHolder=new ItemViewHolder(view);
return itemViewHolder;
}
#Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
Item item=itemList.get(position);
holder.timeDifference.setText(item.timeDifference);
holder.title.setText(item.title);
holder.timeCounter.setText(item.timeCounter);
holder.externalSystem.setText(item.externalSystem);
holder.type.setText(item.type);
holder.project.setText(item.project);
MY IDEA
"holder.timeDifference.setTextColor(Color.parseColor(item.color));"
}
#Override
public int getItemCount() {
if(itemList!=null)
return itemList.size();
else
return 0;
}
public static class ItemViewHolder extends RecyclerView.ViewHolder
{
public CardView cardViewItem;
public TextView title;
public TextView project;
public TextView externalSystem;
public TextView timeDifference;
public TextView timeCounter;
public TextView type;
public ItemViewHolder(View itemView)
{
super(itemView);
cardViewItem=(CardView)itemView.findViewById(R.id.card_view_item);
title=(TextView)itemView.findViewById(R.id.title);
project=(TextView)itemView.findViewById(R.id.project);
externalSystem=
(TextView)itemView.findViewById(R.id.external_system);
timeDifference=
(TextView)itemView.findViewById(R.id.time_difference);
timeCounter=(TextView)itemView.findViewById(R.id.time_counter);
type=(TextView)itemView.findViewById(R.id.type);
}
EDIT: I think I found a way, but I don't know if it's the best one
The solution would involve changing your Item class a little.
Your problem is that you're not passing over the boolean trigger to your RecyclerView.Adapter from your Activity properly. E.g. isEndedBoolean's value to know what state the item is in. You have the right idea in the use of all three classes.
What I would suggest do is create a constructor in your Item class passing the values from your Activity to be used in your adapter. I feel it's easier to use getters and setters rather than assigning the variables straight from code like you have.
So let's begin,
boolean isEnded;
if(!endedAt.equals("null")) {
endedAt = endedAt.substring(endedAt.indexOf("T") + 1, endedAt.lastIndexOf(":"));
isEnded=true;
} else {
endedAt="ongoing";
isEnded=false;
}
String timeDifference = createdAt+" - "+endedAt;
if(externalSystem.equals("null")) {
externalSystem="";
} else {
externalSystem = externalSystem;
}
Log.i("attr",externalSystem);
items.add(new ItemModel(isEnded, timeDifference, externalSystem);
itemAdapter=new ItemAdapter(this, items);
recyclerView.setAdapter(itemAdapter);
itemAdapter.notifyDataSetChanged();
You'll notice how I'm adding a new ItemModel for each row of variables inside the RecyclerView to the array and then passing it that array to the Adapter. This is so that it's easier to know what variables are being passed to the Model and thus the corresponding row at the position inside the Adapter.
An example of the ItemModel class would look something like:
public class ItemModel {
// Getter and Setter model for recycler view items
private boolean isEnded;
private String timeDifference;
private String externalSystem;
//other variables, title etc etc
public ItemModel(boolean isEnded, String timeDifference, String externalSystem) {
this.isEnded = isEnded;
this.timeDifference = timeDifference;
this.externalSystem = externalSystem;
//only pass to the model if you can access it from code above otherwise to assign the variables statically like you have.
}
public boolean getIsEnded() {return isEnded;}
public String getTimeDifference() {return timeDifference;}
public String getExternalSystem() { return externalSystem; }
}
The information above is just a guideline for you to create a more efficient model framework to pass the data rather than using static variables.
Now to solve your problem you need to check if (item.getIsEnded()) and then change the text color corresponding to that if condition.
RecyclerView.Adapter onBindViewHolder would look like:
#Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
ItemModel item =itemList.get(position);
holder.timeDifference.setText(item.getTimeDifference());
holder.title.setText(item.title);
holder.timeCounter.setText(item.timeCounter);
holder.externalSystem.setText(item.getExternalSystem());
holder.type.setText(item.type);
holder.project.setText(item.project);
if (item.getIsEnded() {
holder.timeDifference.setTextColor(item.color);
} else {
holder.timeDifference.setTextColor(item.color);
}
}
The purpose of the Adapter is to inflate a layout, bind components to that layout and perform functionality to the items corresponding to the layout at the dedicated position. You need to know which item in your list is in which state, you won't be able to do that from your Activity alone. Be mindful of how useful the Adapter is in keeping the code from your Activity separate from the actual activity of your RecyclerView.
Overview: I'm having a chat application. Till now, I was using CursorAdapter with a Listview to load my chat items in the list. But now, I'm planning to refactor the code to use RecyclerView with RecyclerView.Adapter and a "Load More" functionality like whatsapp.
Issue: Memory consumption. With CursorAdapter, items not in viewable area were getting Garbage Collected, but now since I'm using an ArrayList of my CustomModal, once you load all the items in the list (by clicking on the "Load More" button) I'm seeing high memory consumption in the memory logs (No Garbage Collection).
My guess is now, I'm loading all the items in an ArrayList and that is causing the issue. Is that it?
Is there a way to avoid the issue or optimize the problem?
EDIT:
Can't post the complete code here, but here is a snippet of the kind of Adapter that I've implemented:
public class MessageAdapter extends RecyclerView.Adapter<MessageAdapter.MyViewHolder> {
private ArrayList<MyModal> mMyModals;
public MessageAdapter(ArrayList<MyModal> mMyModals) {
this.mMyModals = mMyModals;
//... Some fields initialization here
}
public void changeList(ArrayList<MyModal> myModals, boolean isLoadMoreEnabled){
this.mMyModals = myModals;
//... Some fields initialization here
notifyDataSetChanged();
}
public void toggleLoadMore(boolean isLoadMoreEnabled){
if(isLoadMoreEnabled){
//..Checks if load more is already enabled or not
//..If not then enables it by adding an item at 0th poition of MyModal list
//..Then notifyDataSetChanged()
}else{
//..Checks if load more is already disabled or not
//..If not then disables it by removing an item at 0th poition of MyModal list
//..Then notifyDataSetChanged()
}
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
MyViewHolder messageViewHolder = null;
View itemLayoutView = null;
MyModal.MessageType messageType = MyModal.MessageType.getMessageTypeFromValue(viewType);
switch (messageType){
case MESSAGE_TYPE1:
itemLayoutView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout1, null);
messageViewHolder = new Type1ViewHolder(itemLayoutView);
break;
case MESSAGE_TYPE2:
itemLayoutView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout2, null);
messageViewHolder = new Type2ViewHolder(itemLayoutView);
break;
}
return messageViewHolder;
}
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
final MyModal myModal = mMyModals.get(position);
MyModal.MessageType messageType = myModal.getMessageType();
holder.initialize(myModal);
}
#Override
public int getItemCount() {
return (mMyModals != null)?mMyModals.size():0;
}
#Override
public int getItemViewType(int position) {
return mMyModals.get(position).getMessageType().getValue();
}
public abstract class MyViewHolder extends RecyclerView.ViewHolder {
public MyViewHolder(View itemLayoutView) {
super(itemLayoutView);
}
public abstract void initialize(MyModal myModal);
}
class Type1ViewHolder extends MyViewHolder {
//...Variables
public Type1ViewHolder(View itemLayoutView) {
super(itemLayoutView);
//...variables initialization here
}
#Override
public void initialize(MyModal myModal) {
//...Setting values in view using myModal
}
}
class Type2ViewHolder extends MyViewHolder {
//...Variables
public TextViewHolder(View itemLayoutView) {
super(itemLayoutView);
//...variables initialization here
}
#Override
public void initialize(MyModal myModal) {
//...Setting values in view using myModal
}
}
}
First of all :
public void changeList(ArrayList<MyModal> myModals, boolean isLoadMoreEnabled){
this.mMyModals = myModals;
//... Some fields initialization here
notifyDataSetChanged();
}
Here you are creating a new arraylist and assigning it to your mMyModals. This means there are 2 arraylists at this point and they take up twice the amount of space than required. GC doesnt work the way you expect it to. Since the arraylist is initialized in your activity it will persist as long as the arraylist persists and so will the initial arraylist.
Instead of creating a new arraylist in your activity and passing it to changeList. Just clear your old arraylist and pass that.And also in adapter changeList method you can do the below
public void changeList(ArrayList<MyModal> myModals, boolean isLoadMoreEnabled){
this.mMyModals.clear();
this.mMyModels.addAll(myModels);
//... Some fields initialization here
notifyDataSetChanged();
}
Please let me know if i am not clear. Also show your activity code if this does not work.
Instead of replacing the whole ArrayList and calling notifyDataSetChanged, try adding the items to the ArrayList and then call notifyItemRangeInserted(int positionStart, int itemCount), maybe that could work. Also, you dont have to replace the Adapter's ArrayList. Your Activity/Fragment probably has the same ArrayList, just editing this list in your Activity/Fragment and then calling notifyItemRangeInserted(int positionStart, int itemCount) should do the trick. Also, instead of retrieving all the messages, you could also try to only get the next X amount of messages, so you wont retrieve the messages you already retrieved before (if you didn't do that already).
Hello I need help to open a new fragment and pass data when clicked on my Recycler CardView Grid.
Android Grid Image
I want to click on for example the champion Aatrox (first grid) and open a new fragment with Aatrox InformatiĆ³n. the same with the others champions of League of Legends.
I know that is inside of onClick function but I dont know how to do it.
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.itemView.setClickable(true);
holder.itemView.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
}
});
Here is my full ChampAdapter.java
public class ChampAdapter extends RecyclerView.Adapter<ChampAdapter.ViewHolder> {
public List<ChampionItemModel> champItem;
public ChampAdapter(List<ChampionItemModel> champItem){this.champItem = champItem;}
public class ViewHolder extends RecyclerView.ViewHolder{
TextView champName;
TextView roleChamp;
ImageView champImg;
public ViewHolder(View itemView) {
super(itemView);
this.champName = (TextView)itemView.findViewById(R.id.champ_name);
this.roleChamp = (TextView)itemView.findViewById(R.id.champ_role);
this.champImg = (ImageView)itemView.findViewById(R.id.champ_image);
}
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_champs,parent,false);
ViewHolder viewHolder = new ViewHolder(view);
return viewHolder;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.itemView.setClickable(true);
holder.itemView.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
}
});
holder.champName.setText(champItem.get(position).champName);
holder.roleChamp.setText(champItem.get(position).roleChamp);
holder.champImg.setImageResource(champItem.get(position).champImg);
}
#Override
public int getItemCount() { return champItem.size();}
}
First you should embed the RecyclerView inside a fragment, like you normally would, let's call it ChampionOverviewFragment.
Now you should have a SingleChampionFragment with a static newInstance method that accepts as parameters everything that you need to build the champion information (for example a String with the id of your champ). We want to open this fragment when we click on one of the cards in your cardview.
Your activity now only has one HostFragment that you fill with the ChampionOverviewFragment in its onCreate method. See my answer on how to create nested fragments.
Your onClick method can now look like this:
#Override
public void onClick(View v){
((MainActivity) holder.itemView.getContext()).openChampionFragment(holder.getChampionId);
}
Of course, then your MainActivity has to include the following method:
public void openChampionFragment(String id)
this.hostFragment.replaceFragment(SingleChampionFragment.newInstance(id));
}
If you also need backstack navigation, refer to the tutorial I linked in the other answer.
Below is a general method on how to communicate between fragments, so it should be applicable to your issue also.
Place the recyclerView inside a fragment.
I am assuming you are able to get the position of the Adapter. Put recyclerview in a fragment call RV and it's response is to be seen in fragment say RVvalues. You use an interface called PassRVValues
Code in RV fragment:
public class RV extends Fragment{
int RVposition;
PassRVValues passRVValues;
//
your code for recyclerview and other things
//
RVposition = position_value_obtained;
passRVValues.sendToOtherFragment(RVposition);
Here's the code for the interface. Make a new java class having just the interface.
public interface PassRVValues{
sendToOtherFragment(int value_from_RVFragment);
}
Code in the activity
public class MainActivity implements PassRVValues{
//
some code
//
#Override
public void sendToOtherFragment(int use_value_from_RVFragment){
//
Do something with data if you want
//
RVvalues Frag = (RVValues)getFragmentManager().findFragmentById(R.id.rvvalues);
Frag.ShowDataBasedOnPositionInRV(something_based_from_any_process_in_sendToOtherFragment_Method);
}
Now for the code in the RVValues fragment
public class RVValues extends Fragment{
//again any codes//
public void ShowDataBasedOnPositionInRV(int data_based_on_RV_position){
//do something//
}
This is the way to implement inter-fragment communication in the simplest manner. Hope this helps!
Cheers!