Recyclerview in Android Studio Error - "null object reference" - java

I'm new in android and I have just screwed something.
I've used recycleView template and it worked fine but I wanted to change type of my object from string to "TagsManagerObject" which contains
private String tagName;
private String gender;
private String mAgeMin;
private String mAgeMax;
private String mDistance;
since then I'm stuck with this error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.tinderapp, PID: 10921
java.lang.NullPointerException: Attempt to invoke interface method 'void com.example.tinderapp.Tags.TagsManagerAdapter$ItemClickListener.onDeleteClick(int)' on a null object reference
at com.example.tinderapp.Tags.TagsManagerAdapter$ViewHolder$1.onClick(TagsManagerAdapter.java:78)
at android.view.View.performClick(View.java:6614)
at android.view.View.performClickInternal(View.java:6587)
at android.view.View.access$3100(View.java:787)
at android.view.View$PerformClick.run(View.java:26122)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:201)
at android.app.ActivityThread.main(ActivityThread.java:6831)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:927)
My adapter looks like:
public class TagsManagerAdapter extends RecyclerView.Adapter<TagsManagerAdapter.ViewHolder>{
private LayoutInflater mInflater;
private ImageView mDeleteImage;
private List<TagsManagerObject> mTagsManagerObject;
private ItemClickListener mItemClickListener;
// data is passed into the constructor
public TagsManagerAdapter(Context context,List<TagsManagerObject> TagsManagerObject) {
this.mInflater = LayoutInflater.from(context);
this.mTagsManagerObject = TagsManagerObject;
}
// inflates the row layout from xml when needed
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.item_tags_manager, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
// String tag = TagsManagerObject.get(position);
holder.tagName.setText("#"+mTagsManagerObject.get(position).getTagName());
holder.gender.setText(mTagsManagerObject.get(position).getGender());
holder.distance.setText(mTagsManagerObject.get(position).getmDistance());
holder.tagAge.setText(mTagsManagerObject.get(position).getmAgeMin() + "-" + mTagsManagerObject.get(position).getmAgeMax());
}
// binds the data to the TextView in each row
// total number of rows
#Override
public int getItemCount() {
return mTagsManagerObject.size();
}
public void setClickListener(ItemClickListener mItemClickListener) {
this.mItemClickListener = mItemClickListener;
}
// stores and recycles views as they are scrolled off screen
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private TextView tagName,gender,tagAge,distance;
ViewHolder(View itemView) {
super(itemView);
tagName= itemView.findViewById(R.id.tag);
gender = itemView.findViewById(R.id.tag_gender);
distance = itemView.findViewById(R.id.tag_distance);
tagAge = itemView.findViewById(R.id.tag_age);
mDeleteImage = itemView.findViewById(R.id.tag_delete);
itemView.setOnClickListener(this);
mDeleteImage.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = getAdapterPosition();
if(position!=RecyclerView.NO_POSITION){
mItemClickListener.onDeleteClick(position); <<--- HERE IS THE ERROR
}
}
});
}
#Override
public void onClick(View view) {
if (mItemClickListener != null) mItemClickListener.onItemClick(view, getAdapterPosition());
}
}
// convenience method for getting data at click position
String getItem(int id) {
return mTagsManagerObject.get(id).toString();
}
// allows clicks events to be caught
// parent activity will implement this method to respond to click events
public interface ItemClickListener {
void onItemClick(View view, int position);
void onDeleteClick(int position);
}
}
So when i click delete button it crashes. Does anyone knows why?
Thanks.

You got this exception as you you call onDeleteClick(position) on an object that is null, so you have to set a value to the mItemClickListener before calling this method onDeleteClick(position)
In your code, you have to call setClickListener() of your adapter in your activity/fragment that uses this adapter.

You need to do two things.
First, you need to check mItemClickListener for null. Doing this will prevent from null pointer exception.
mDeleteImage.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = getAdapterPosition();
if(position!=RecyclerView.NO_POSITION){
if(mItemClickListener !=null){
mItemClickListener.onDeleteClick(position); <<--- HERE IS THE ERROR
}
}
}
}
);
And the second thing is you should set/call the setClickListener for the Activity/Fragment, using the instance of RecyclerviewAdapter.
Doing this will make the Activity/Fragment implement your ItemClickListener and onDeleteClick() method.
adapter.setClickListener(this);
Or, you can also use the Anonymous class here.

Thanks guys for help. The problem was that in my acvitivy I have:
1.Created adapter with empty taglist
2.set onclicklistener
adapter.setOnItemClickListener(new TagsManagerAdapter.OnItemClickListener() {
#Override
public void onItemClick(int position) {
}
#Override
public void onDeleteClick(int position) {
System.out.println("works?");
removeItem(position);
}
});
And then add items.
Instead
adapter.notifyDataSetChanged();
Ive used:
adapter = new TagsManagerAdapter(myTagsList);
and that caused problem, that it coudnt find my listener

Related

Android Studio Firebase DataChange Async and Bundle Issue (Java)

I have been getting certain issues with the onDataChange method when I am calling from Firebase Realtime Database. These issues would include data disappearing outside of the DataChange method. I tried a solution from this link,
How to return DataSnapshot value as a result of a method?, it has worked, however, when I tried passing it to a fragment bundle within the callback method, it says the values are null and there isn't any data in it. Is there any workaround for this problem that I am facing? Help is really appreciated, thanks!
import edu.ntu.ssp4_rzdns_outhink.R;
import edu.ntu.ssp4_rzdns_outhink.modals.Attraction;
public class MostPopularRecyclerViewAdapter extends RecyclerView.Adapter<MostPopularRecyclerViewAdapter.ViewHolder>{
private static final String TAG = "MostPopularRecyclerViewAdapter";
private static final String SHARED_PREFS = "attractionsFile";
private ArrayList<Attraction> pops;
private Attraction attraction;
private Context mContext;
private FragmentManager fm;
private Bundle bundle;
SharedPreferences.Editor editor;
SharedPreferences attractionFile;
public MostPopularRecyclerViewAdapter(ArrayList<Attraction> pops, Context mContext, FragmentManager fm, Bundle bundle) {
this.pops = pops;
this.mContext = mContext;
this.fm = fm;
this.bundle = bundle;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
Log.d(TAG, "OnCreateViewHolder Called");
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_more_list, parent, false);
ViewHolder holder = new ViewHolder(view);
return holder;
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
Log.d(TAG, "onBindViewHolder Called");
Glide.with(mContext).asBitmap().load(pops.get(position).photo_url).into(holder.attractionImage);
holder.locationName.setText(pops.get(position).att_name);
holder.locationRating.setText(pops.get(position).att_rating.toString());
holder.locationAddress.setText(pops.get(position).att_address);
holder.parentLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String attraction_id = pops.get(holder.getBindingAdapterPosition()).id;
readData(new FirebaseCallback() {
#Override
public void onCallBack(Attraction attr) {
bundle.putString("attid", attr.id);
bundle.putString("name", attr.att_name);
bundle.putString("address", attr.att_address);
bundle.putString("desc", attr.att_desc);
bundle.putDouble("rating", attr.att_rating);
bundle.putString("url", attr.att_url);
bundle.putSerializable("ophrs", attr.att_op_hr);
bundle.putSerializable("adminrate",attr.att_admin_rate);
bundle.putString("photo_url", attr.photo_url);
}
},attraction_id);
}
});
}
#Override
public int getItemCount() {
return pops.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder{
ImageView attractionImage;
TextView locationName;
TextView locationAddress;
TextView locationRating;
RelativeLayout parentLayout;
public ViewHolder(#NonNull View itemView){
super(itemView);
attractionImage = itemView.findViewById(R.id.viewmoreImage);
locationName = itemView.findViewById(R.id.viewmoreName);
locationAddress = itemView.findViewById(R.id.viewmoreLocation);
locationRating = itemView.findViewById(R.id.viewmoreRating);
parentLayout = itemView.findViewById(R.id.parent_layout_view_more);
}
}
private void readData(FirebaseCallback firebaseCallback, String attrId){
Query query = FirebaseDatabase.getInstance().getReference("attractions");
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
if (!snapshot.exists()) {
System.out.println("Attraction Does Not Exist");
} else {
for (DataSnapshot attr : snapshot.getChildren()) {
if (attr.getKey().equals(attrId)) {
attraction = attr.getValue(Attraction.class);
attraction.setId(attr.getKey());
}
}
firebaseCallback.onCallBack(attraction);
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
}
private interface FirebaseCallback{
void onCallBack(Attraction attraction);
}
}
According to your last comment:
When I try to put these variables in the bundle, it’ll just disappear and become null.
Please note that this is the expected behavior. There are multiple inconsistencies in your code. First of all, that's not how you should handle the click event in your adapter class. Attaching a click listener to each element you display, isn't a feasible solution. You should create an interface for that as you can see in this repo.
Now, each time you click a particular element in your RecyclerView, you call the readData() method. When you receive the data, you add that data to the Bundle. The problem is that the operation that is opening the new fragment is happening faster than you get the data from the database. That's the reason why you are getting null.
To solve this, you either navigate to the next fragment, only when the data is available or you can make another call, right from the second fragment.

Android RecyclerView pass position is always the last one

Good day, I want to pass position from RecyclerView's adapter to MainActivity, so I design a interface:
in RecyclerViewAdapter:
publuc interface recyclerViewAdapterListener{
void onDeviceConnect(data, position)
}
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
//here I call onDeviceConnect
holder.button.setOnClickListener(new View.OnClickListener(){
public void onClick(View view) {
listener.onDeviceConnect(data, position);
}
in MainActicity:
private int pos;
public void onDeviceConnect(data,position){
pos = position;
}
I know the problem is pos = position, that will be always get the last position.
but I need data and position from RecyclerView, because when my device connected,
It will change RecyclerView's item.
ex : (in MainActivity)
case BleService.ACTION_NOTIFY_ON: //here my device is connected.
updateStatus(mac,status); //update status
private void updateStatus(mac,status){
Adapter.updateItem(data, pos); //call adapter's item
}
ex : (in RecyclerViewAdapter)
public void updateItem(data,pos){
if (dataList.size() != 0){
dataList.set(pos, data);
notifyItemChanged(pos);
}
is any good idea can fix it , Thanks.
in MainActicity:
`
public class MainActicity extends AppCompatActivity implements YourRecyleAdaptor.recyclerViewAdapterListener{
private int pos;
#Override
public void onDeviceConnect(data,position){
pos = position;
}
`
Try to replace the position with holder.getAdapterPosition()
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
//here I call onDeviceConnect
holder.button.setOnClickListener(new View.OnClickListener(){
public void onClick(View view) {
listener.onDeviceConnect(data, holder.getAdapterPosition());
}
Or you can also setOnClickListener in the constructor of the ViewHolder class

Adding new button and spinner for each button click

In the activity when user click on add client button I want to add new view to the screen which contains a spinner with list of client names retrieved from api and a button that will do some action on click.
So I thought I would use a recycleview and adapter for this but I think I'm wrong
in the activity I have the adapter
private ClientAdapter clientAdapter;
When I retrieve clients name from API I set the adapter as
clientRecyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
clientAdapter= new clientAdapter(clientList , this , this);
clientRecyclerView.setAdapter(podAdapter);
At this point I don't want the recycle view to render anything until user click on add new client button then I want to display one item that has spinner with client names and a button.
Then if he clicks again on add client button I want to show another spinner and button and so on.
However now I'm having 3 clients so recycleview render 3 view items which make sense.
But what the trick that I should do to achieve my goal?
Here's my adapter
public class ClintsAdapter extends RecyclerView.Adapter<ClintsAdapter.ViewHolder> {
private List<Clients> clientsList;
private EventListener listener;
public ClintsAdapter(List<Clients> clientsList, EventListener listener , Context context) {
this.clientsList = clientsList;
this.EventListener = listener;
}
#NonNull
#Override // To inflate view
public ClintsAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.listitem_client, parent, false);
ViewHolder viewHolder = new ViewHolder(view, listener);
return viewHolder;
}
#Override
public void onBindViewHolder(#NonNull ClintsAdapter.ViewHolder holder, int position) {
ClintsAdapter = new ArrayAdapter<Client>(context, R.layout.spinner_text_view, clientsList);
ClintsAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.clientSpinner.setAdapter(ClintsAdapter);
holder.clientSpinner.setTitle("Choose client");
}
#Override
public int getItemCount() {
if (clientsList == null)
return 0;
return clientsList.size();
}
public interface PODListener {
void onClick(int position);
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private SearchableSpinner clientSpinner , collectMethodSpinner;
EventListener listener;
public ViewHolder(View itemView, final EventListener listener) {
super(itemView);
this.listener = podListener;
clientSpinner = itemView.findViewById(R.id.spinner_client);
btnComment = itemView.findViewById(R.id.btn_comment);
btnComment.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if ( listener != null ) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION){
listener.onClick(position);
}
}
}
});
}
#Override
public void onClick(View v) {
}
}
}
and here's my list item
From comments:
The problem is I'm passing list of clients to the adapter (size of 3) then the adapter render 3 items. I don't want this behavior I want to have 0 item if user click on add I will render one item and so on
You are using a single ArrayList<Client> for two different purposes:
The list of clients to choose from in the spinner
The number of spinners to display in the RecyclerView.
These are two separate things, so you need two separate lists.
You can do that with just adding integer value for your ClientsAdapter. Set its default value as 0 and create a method for changing it's value. When you want to add new item (new Spinner and Button) use that method and notify your adapter.
Add a new field called count for your ClientsAdapter.
private int count;
Inside constructor assign its value to 0. So on start its value will be 0 and RecyclerView will show nothing.
public ClintsAdapter(List<Clients> clientsList, EventListener listener , Context context){
this.clientsList = clientsList;
this.EventListener = listener;
count = 0;
}
Change getItemCount method's return value. According to your code getItemCount returns size of your List. That List is for Spinner and has no relation with this method. Instead of returning your List's size return count.
#Override
public int getItemCount() {
return count;
}
Create a method for changing count's value. count starts with 0 (assigned it 0 inside constructor) and when you click Button (add new Spinner and Button) this method will change its value.
public void addItem(int count) {
this.count = count;
}
Whenever you click Button simply call addItem method and pass new count value and notify your clientAdapter.
addClient.setOnClickListener(v -> {
int count = clientRecyclerView.getChildCount();
clientAdapter.addItem(count+1);
clientAdapter.notifyItemInserted(count);
});
NOTE: I don't get it why you're setting podAdapter for RecyclerView.
clientRecyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
clientAdapter= new clientAdapter(clientList , this , this);
clientRecyclerView.setAdapter(podAdapter);
You're creating clientAdapter reference for your ClientsAdapter but while setting adapter for RecyclerView, you're using different reference (podAdapter).
Full code for ClientsAdapter:
public class ClintsAdapter extends RecyclerView.Adapter<ClintsAdapter.ViewHolder> {
private List<Clients> clientsList;
private EventListener listener;
private int count;
public ClintsAdapter(List<Clients> clientsList, EventListener listener , Context context) {
this.clientsList = clientsList;
this.EventListener = listener;
count = 0;
}
#NonNull
#Override // To inflate view
public ClintsAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.listitem_client, parent, false);
ViewHolder viewHolder = new ViewHolder(view, listener);
return viewHolder;
}
#Override
public void onBindViewHolder(#NonNull ClintsAdapter.ViewHolder holder, int position) {
ClintsAdapter = new ArrayAdapter<Client>(context, R.layout.spinner_text_view, clientsList);
ClintsAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.clientSpinner.setAdapter(ClintsAdapter);
holder.clientSpinner.setTitle("Choose client");
}
public void addItem(int count) {
this.count = count;
}
#Override
public int getItemCount() {
return count;
}
public interface PODListener {
void onClick(int position);
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private SearchableSpinner clientSpinner , collectMethodSpinner;
EventListener listener;
public ViewHolder(View itemView, final EventListener listener) {
super(itemView);
this.listener = podListener;
clientSpinner = itemView.findViewById(R.id.spinner_client);
btnComment = itemView.findViewById(R.id.btn_comment);
btnComment.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if ( listener != null ) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION){
listener.onClick(position);
}
}
}
});
}
#Override
public void onClick(View v) {
}
}
}

Android: customize a single row in RecyclerView

I have a recyclerView behanving like a list, how do I change the background color of only one view? The item I want to change the color's ID is "1", I thought about getting it in the activity by it's ID and add it a decorator, but I don't know how to, or if it's even possible.
Inside the adapter:
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.setEventNameName(i + "");
holder.settheColor(Color.parseColor("#000000"));
}
static int i;
class ViewHolder extends RecyclerView.ViewHolder{
public TextView eventName;
public RelativeLayout theLayout;
public ViewHolder(final View itemView) {
super(itemView);
eventName = (TextView)itemView.findViewById(R.id.eventName);
theLayout = (RelativeLayout)itemView.findViewById(R.id.backgroundevent);
theLayout.setId(++i);
theLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (getAdapterPosition()>0){
removeItem(getAdapterPosition());
}else {
addItem("");
}
}
});
}
public void settheColor(int theColor){
theLayout.setBackgroundColor(Color.parseColor(String.valueOf(theColor)));
}
public void setEventNameName(String TheEventName){
eventName.setText(TheEventName);
}
}
I feel you can do it in this way --
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
currentListItem = List.get(position);
if(position == 0) //this will let you know the first child item
{
holder.textview.setBackgroundColor(Color.RED);
}
}
Hope this helps!
Pretty simple, use the onBindViewHolder method of your adapter, and change the color of the view (which should be referenced in your ViewHolder)
Follow this example if you are new to the Recyclerview concept.

Interface value is always null

I am using custom recycler view and in adapter class i have implemented interface which is always null on button click. Here is my adapter class.
public class FeedListAdapter extends
RecyclerView.Adapter<AddtoCartHolder> {
private OnFeedItemClickListener onFeedItemClickListener;
public FeedListAdapter(Activity activity, ArrayList<CartItem> feedItems) {
this.activity = activity;
this.feedItems = feedItems;
this.filteredfeedItems = feedItems;
inflater = LayoutInflater.from(activity);
}
public void setOnFeedItemClickListener(OnFeedItemClickListener onFeedItemClickListener) {
this.onFeedItemClickListener = onFeedItemClickListener;
}
#Override
public AddtoCartHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(
R.layout.feed_item, parent, false);
AddtoCartHolder viewHolder = new AddtoCartHolder(v);
setupClickableViews(v, viewHolder);
return viewHolder;
}
#Override
public void onBindViewHolder(final AddtoCartHolder holder, int position) {
CartItem item = (CartItem) filteredfeedItems.get(position);
holder.price.setText((String.valueOf(item.getProductName()) + ""));
holder.location.setText((String.valueOf(item.getQuantity())) + "");
}
private void setupClickableViews(final View view, final AddtoCartHolder cellFeedViewHolder) {
cellFeedViewHolder.plus.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(onFeedItemClickListener !=null){
onFeedItemClickListener.onAddClick(v, cellFeedViewHolder.getAdapterPosition());
}
else{
Toast.makeText(activity, "Data is null", Toast.LENGTH_LONG).show();
}
}
});
}
public interface OnFeedItemClickListener {
void onAddClick(View v, int position);
}
#Override
public int getItemCount() {
return filteredfeedItems.size();
}
I am always getting null whenever clicking on button really down know why it is coming null...
Here is my fragment class which have implemented interface.
public class MyFragment extends Fragment implements FeedListAdapter.OnFeedItemClickListener{
// the method
#Override
public void onAddClick(View v, int position) {
// TODO Auto-generated method stub
Snackbar.make(clContent, "Product removed from cart!",
Snackbar.LENGTH_SHORT).show();
}
You must be instantiating a FeedListAdapter in your fragment correct buddy ???
Like using statement :
FeedListAdapter adapter = new FeedListAdapter(this.getActivity(),your_array_list)
After instantiating your adapter just call your adapter's setOnFeedItemClickListener with 'this' as argument :) That's all :)
adapter.setOnFeedItemClickListener(this)
Hope my answer helped you :) Happy coding buddy :)
By the looks of things your aren't setting your listener. Thus, onFeedItemClickListener is always null.
Also MyFragment isn't actually doing anything, you haven't inflated a view, overridden onCreateView(...), etc.
There's a few things that you could definitely change to improve your implementation. But to get your listener working:
Just get rid of MyFragment you don't appear to be using it properly.
Move your implements FeedListAdapter.OnFeedItemClickListener to your Activity. i.e. Make your Activity implement your OnFeedItemClickListener interface rather than MyFragment (which doesn't appear to be doing anything).
Make FeedListAdapter set the listener in its constructor:
public FeedListAdapter(Activity activity, ArrayList<CartItem> feedItems)
{
this.activity = activity;
// Assume we the activity implements OnFeedItemClickListener
setOnFeedItemClickListener((OnFeedItemClickListener)activity);
this.feedItems = feedItems;
this.filteredfeedItems = feedItems;
inflater = LayoutInflater.from(activity);
}
Please keep in mind that this is a pretty bad implementation and you can definitely improve on it but for the purpose of the question, it's sufficient.

Categories