RecyclerView onItemClick() imitation, is it good practice? - android

I saw some different ways of implementing onItemClickListener for RecyclerView. My favourite is to use interface callback:
Interface:
public interface OnClickListener {
void onCardClick(View v);
void onFavouriteButtonClick(View v);
void onRemoveClick(View v);
//Other clickable areas
}
In adapter:
currentView.favouriteButton.setTag(currentItem.getId());
currentView.favouriteButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (OnClickListener != null){
OnClickListener.onFavouriteButtonClick(v);
}
}
});
In main activity/fragment
adapter = new CardAdapter( .... new OnClickListener() {
#Override
public void onFavouriteButtonClick(View v) {
int id = Integer.parse(v.getTag().toString());
//Do your stuff
}
....
});
So, I have some questions:
is it good way of handling things?
will performance be slow if number of items will grow?
any reasons why I shouldn't use this approach?

I think you could improve upon this by not passing the view outside of the adapter.
// Create an object that encapulates the information for each card
public class CardInfo {
// Whatever you want to show in your card
}
// Create a listener for items instead of views
public interface OnCardAction {
void onCardSelected(CardInfo cardInfo);
void onFavoriteSelected(CardInfo cardInfo);
void onCardRemoved(CardInfo cardInfo);
}
// In your adapter
favoriteButton.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
// You many need to check if onCardActionLisetner is null
CardInfo cardInfo = getItemAt(viewHolder.getAdapterPosition());
onCardActionListener.onFavoriteSelected(cardInfo);
}
});
NOTE: If you are passing the listener in the constructor can either make it required (throw error if it is null) or not even create View.OnClickListner to reduce the number of null checks.

Related

onClickListener is called earlier than Fragment onCreateView

I have an Recycler.Adapter and my onBindViewHolder is like this:
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
final String url = urls.get(position);
final width_height wh = whs.get(position);
holder.imageView.setClickable(true);
Picasso.with(context)
.load(url).resize(wh.width, wh.height).centerCrop()
.into(holder.imageView);
holder.imageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dialogFragment.show(fragmentManager, "");
dialogFragment.setImage(url);
dialogFragment.setRating(0);
}
});
holder.textView.setText(position + "");
holder.textView.setClickable(true);
holder.textView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dialogFragment.show(fragmentManager, "");
dialogFragment.setImage(url);
dialogFragment.setRating(0);
}
});
}
The dialogFragment is already instantiated in the contructor of Adapter. However, its onCreateView has not been called yet when the holder is clicked. And the setImage and setRating need to modify its imageView and RatingBar, which are null unless the onCreateView of the fragment is called. Is there a way to resolve this?
That's because DialogFragment.show commits asynchronously. In your case you'd want it to use DialogFragment.showNow instead so it commits synchronously.
#Override
public void onClick(View v) {
dialogFragment.showNow(fragmentManager, "");
dialogFragment.setImage(url);
dialogFragment.setRating(0);
}
Otherwise I'd suggest to create new DialogFragment every time the button is clicked, and pass image url and rating as arguments before it is shown.
A more correct way would be to have the setImage method and setRating method check for null. If null, they should save the value to variable, and onCreate should use those values to set.
For example (not tested);
void setImage(src) {
if (mImage == null)
mImageSource = src;
else
mImage.setImage(src);
}
void onCreateView(...) {
...
if (mImageSource != null)
mImage.setImage(mImageSource);
mImageSource = null;
}
This way you can correctly use the asynchronous dialog calls.

Card View Click on card to move to next activity

I am working on a project in android and i just learn about card view class.
I made a card who generates a toast when user clicks on it.
But i also want my card to call another activity when user clicks on it.
I am posting part of my code below.
btnProceed.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
showToast("Proceed to the next step");
Intent intent = new Intent(MyLocationUsingLocationAPI.this, click_picture.class);
startActivity(intent);
}
});
I have made changes in my code as you said but when i click on proceed button my app crashes.What is wrong with code?
the main idea here is to define your actionClickListener
1. create a custom recycleView Adapter
public class AdapterCustomList extends RecyclerView.Adapter<RecyclerView.ViewHolder>
2. define onItemClickListener interface
public interface OnItemClickListener {
void onItemClick( whateverArgsYouWant );
}
3. make an attribute of the interface and define a setter for it
private OnItemClickListener mOnItemClickListener;
public void setOnItemClickListener(final OnItemClickListener mItemClickListener) {
this.mOnItemClickListener = mItemClickListener;
}
4. append a listener to each item while its created in the adapter class
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
...
OriginalViewHolder vItem = (OriginalViewHolder) holder;
vItem.baseCard.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (mOnItemClickListener != null) {
mOnItemClickListener.onItemClick( whateverArgsYouWant );
}
}
});
}
this method will be called when items of recycle view is created (in case you use card view inside a recycle view)
5. use your onClickListener in the Activity you want
AdapterCustomList mAdapter = new AdapterCustomList (getActivity(), recyclerView,yourListItemsHere));
recyclerView.setAdapter(mAdapter);
// on item list clicked
mAdapter.setOnItemClickListener(new AdapterPostList.OnItemClickListener() {
#Override
public void onItemClick( whateverArgsYouWant ) {
...
statements
...
}
});

After clicking a button view is updating on both items of recyclerview

I have a RecyclerView with post cards. So there is a like button in every post card. When I tap on a like button. Sometimes it works fine and updates on current item. But after some scroll when I tap on like button it updates on another item simultaneously.
It doesn't send that data to server. But, only the view is updated. Then when I scroll up and down again. The data goes back to normal.
Problem is in the following onClick method:-
FirebaseDatabase.getInstance().getReference().child("topics").child("likes").child(data.getId()).child(username).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.child("like").exists()){
holder.like.setImageResource(R.drawable.ic_favorite_red_500_36dp);
holder.like.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
FirebaseDatabase.getInstance().getReference().child("topics").child("likes").child(data.getId()).child(username).child("like").removeValue();
}
});
}else if(dataSnapshot.child("dislike").exists()) {
holder.like.setImageResource(R.drawable.brokenheart);
holder.like.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
FirebaseDatabase.getInstance().getReference().child("topics").child("likes").child(data.getId()).child(username).child("dislike").removeValue();
}
});
}else{
holder.like.setImageResource(R.drawable.ic_favorite_border_red_500_36dp);
holder.like.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
FirebaseDatabase.getInstance().getReference().child("topics").child("likes").child(data.getId()).child(username).child("dislike").removeValue();
FirebaseDatabase.getInstance().getReference().child("topics").child("likes").child(data.getId()).child(username).child("like").setValue(true);
}
});
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
holder.like.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
FirebaseDatabase.getInstance().getReference().child("topics").child("likes").child(data.getId()).child(username).child("like").removeValue();
FirebaseDatabase.getInstance().getReference().child("topics").child("likes").child(data.getId()).child(username).child("dislike").setValue(true);
return true;
}
});
Full Adapter class https://pastebin.com/UnFGahWT
Try the following:-
Set Your on click listeners in the public ViewHolder(View itemView) method and make Your ViewHolder class implement the View.OnClickListener.
In Adapter add:
public Topic getItem(int position) {
return topics.get(position);
}
In ViewHolder's onClick method add:
int position = getAdapterPosition();
if (position == RecyclerView.NO_POSITION) return;
item = getItem(position);
Thus You will get the exact object You need to change or do something with it.

How to get this from an anonymous method of abstract parent activity

I have an abstract Activity called Animal and two concrete descendant Activities Cat and Dog.
Cat & Dog are to present the same UI, consisting of a single button and so there is a single layout, activity_animal.xml that Animal sets as it's content view in OnCreate.
I want to set the button's OnClickListener in the abstract Animal class by means of an anonymous implementation of OnClickListener
private void setClickHandlers() {
((Button) findViewById(R.id.btn))
.setOnClickListener(new OnClickListener() {
public void onClick(View arg0) {
//how to get a reference to this?
}
});
}
and in onClick I want to make a new Intent. To make the Intent I need a reference to this.
Normally, in anonymous method code like this, I could use syntax such as
EnclosingClass.this
but here, I don't know what the enclosing class will be. At run time, it could be either a Cat or a Dog.
How to do this?
The only way I can think of is to provide an abstract getThis() in Animal which is overridden in each concrete descendant.
You should be able to say Animal.this as your context.
private void setClickHandlers() {
View view = findViewById(R.id.btn);
view.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
Intent intent = new Intent(Animal.this, MyDestinationActivity.class);
...
}
});
}
Animal.this will be either a Cat or Dog but since you only need Context the distinction is irrelevant.
public void onClick(View arg0) {
Activity host = (Activity) arg0.getContext();
}
I'd do it like:
private void setClickHandlers() {
final Animal thiz = this;
((Button) findViewById(R.id.btn))
.setOnClickListener(new OnClickListener() {
public void onClick(View arg0) {
Intent intent = new Intent( thiz, AAAA.class );
}
});
}
You can determine the runtime type by using the instanceof operator.
if (Animal.this instanceof Dog) {
// dog related
}
else {
// cat related
}

Mortar: How-to use Presenter for List Item/Adapter

I using a List with Custom Adapter with ViewHolder and Item Views with Buttons.
I want to handle some stuff by clicking the Buttons in a Presenter.
How can i connect a Presenter with these Item Views?
You can use rx-android to observe AdapterView's click events the following way. It combines fine with MVP pattern used in Mortar.
At first dependencies:
compile 'io.reactivex:rxjava:1.0.4'
compile 'io.reactivex:rxandroid:0.24.0'
Then create observeClicks method in your view class.
public Observable<OnItemClickEvent> observeClicks() {
return WidgetObservable.itemClicks(adapterView);
}
Subscribe to events in your Presenter's onLoad:
private Subscription clicks;
#Override
protected void onLoad() {
super.onLoad();
clicks = getView().observeClicks().subscribe(
new Action1<OnItemClickEvent>() {
#Override
public void call(OnItemClickEvent event) {
// handle click events
}
}
);
}
protected void onDestroy() {
// unsubscribe from events
clicks.unsubscribe();
}
Also you can do mapping OnItemClickEvent to your data inside view already and observe it instead of raw events.
public Observable<YourDataClass> observeClicks() {
return WidgetObservable.itemClicks(adapterView).
map(new Func1<OnItemClickEvent, YourDataClass>() {
#Override
public YourDataClass call(OnItemClickEvent onItemClickEvent) {
YourDataClass item = adapter.getItem(onItemClickEvent.position());
return item;
}
});
}
Hope this comes in handy. At least I'm doing this that way, but certainly it is not the only solution.
ListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//Handle your stuff here.
});
Something like this? Use the position to get the current item out of your List.
Buttons :
// In the adapter
Button butt = (Button) rowView.findViewById(R.id.yourbutton);
butt.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
//Button Action.
}
}

Categories

Resources