Fragment in a Adapter of RecyclerView JAVA - android

I have a fragment Users which has 3 other fragments in it (tabs). For one tab ( called Friends2Fragment ) I made a recycler View and made an adapter for it. In each item of RecyclerView I have a button "Add friend" and I want to call it from Friends2Fragment, not to call it from the adapter because I can't use Firestore Database properly.
RecyclerViewInterface:
public interface RecyclerViewInterface {
void onItemClick(int position, String button_pressed);
}
Friends2Fragment.java :
public void onStart(){
super.onStart();
recyclerView = (RecyclerView) v.findViewById(R.id.recycler);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
friendslist = new ArrayList<>();
myAdapter = new MyAdapter(friendslist,v.getContext());
recyclerView.setAdapter(myAdapter);
------ Firestore operations ------
}
#Override
public void onItemClick(int position, String button_pressed) {
switch ( button_pressed ){
case "ADD_FRIEND":
Log.d(TAG, "item clicked: " + friendslist.get(position).username);
}
}
MyAdapter.java :
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.myViewHolder> {
Context context;
public ArrayList<User> userArrayList;
public MyAdapter(ArrayList<User> userArrayList, Context context) {
this.userArrayList = userArrayList;
this.context = context;
}
public Context getContext() {
return context;
}
public ArrayList<User> getUserArrayList() {
return userArrayList;
}
#NonNull
#Override
public MyAdapter.myViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
MyAdapter.myViewHolder myViewHolder = new MyAdapter.myViewHolder(v);
myViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
((Friends2Fragment)context).onItemClick(myViewHolder.getAdapterPosition(),"ADD_FRIEND");
}
});
return myViewHolder;
}
#Override
public void onBindViewHolder(#NonNull MyAdapter.myViewHolder holder, int position) {
User user = userArrayList.get(position);
holder.usernamerecycle.setText(user.username);
}
#Override
public int getItemCount() {
return userArrayList.size();
}
public void filterList(List<User> filteredList){
userArrayList = (ArrayList<User>) filteredList;
notifyDataSetChanged();
}
public class myViewHolder extends RecyclerView.ViewHolder{
TextView usernamerecycle;
Button addbutton;
View rootview;
public myViewHolder(#NonNull View itemView) {
super(itemView);
rootview = itemView;
usernamerecycle = itemView.findViewById(R.id.usernamerecycler);
addbutton = itemView.findViewById(R.id.addfriendbutton);
}
}
}
The problem is at this line : ((Friends2Fragment)context).onItemClick(myViewHolder.getAdapterPosition(),"ADD_FRIEND"); in onCreateViewHolder method in MyAdapter.
I have this error : Inconvertible types; cannot cast 'android.content.Context' to 'com.example.birthday.Fragments.Friends2Fragment'
Please help me ..

A Fragment isn't a Context (that's not one of its supertypes) so that cast is impossible, that's why you're getting the error.
I think you should organise it like this: your Adapter holds a bunch of User objects, right? It displays those, and you have a click listener on each ViewHolder that knows which index in the User list it's currently displaying, and it wants to inform some listener when it's clicked. That index is an internal detail really, it would make more sense to look up the actual User, and provide that to the listener.
The simplest way is to just provide your fragment as a listener. First store it in your adapter:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.myViewHolder> {
// store a reference to your fragment
private Friends2Fragment listener;
// add a function to provide that fragment
public void setListener(Friends2Fragment: listener) {
this.listener = listener
}
...
public MyAdapter.myViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
...
myViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (listener != null) {
// look up the actual user
User user = userArrayList.get(myViewHolder.getAdapterPosition());
// call a function on your fragment
listener.onItemClick(user, "ADD_FRIEND");
}
}
});
}
Then add the callback function your adapter uses, and also set your fragment on the adapter as a listener:
// Friends2Fragment
// You should REALLY be doing this in onViewCreated or something, so this setup happens once.
// You're losing all your state by creating a new adapter whenever the user returns to the app
public void onStart(){
...
myAdapter = new MyAdapter(friendslist,v.getContext());
// set the fragment as the listener
myAdapter.setListener(this);
recyclerView.setAdapter(myAdapter);
}
// now add the function the adapter calls
private void onItemClick(User user, String someString) {
// handle the clicked user
}
A better way is to create an interface with all the events that need to be handled, and make your Fragment implement those. It breaks the hard association with the Fragment since you could pass any object that implements those functions, and it's also clearer because the interface kinda documents all the data the adapter produces, and that a listener needs to be able to handle. Something like this:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.myViewHolder> {
// the listener is now something that implements the Callbacks interface
private Callbacks listener;
...
// nesting it inside MyAdapter makes the path MyAdapter.Callbacks, which makes it clear
// exactly what it is and what it relates to, and kinda gives the Adapter "ownership"
interface Callbacks {
void addFriend(User user)
}
And then you just make the Fragment implement that interface
public class Friends2Fragment() extends Fragment implements MyAdapter.Callbacks {
...
// implement all the callbacks you need to handle
override public void addFriend(User user) {
// do the thing
}
// set it in the same way, since this Fragment implements MyAdapter.Callbacks
myAdapter.setListener(this);
Which is a bit neater and cleaner, I think - but slightly more work. Also if you notice, I renamed the callback function from the generic handleItemClick to the more specific addFriend - so instead of having to pass a String saying what kind of click it is, you just have a function for each event you want to handle, and you can name them appropriately

Related

how to add a switch statement in a RecyclerView adapter?

I have a RecyclerView with some images,here i want to open different Activitys by clicking on different images...
So, i think using the switch statement in the onClick of the adapter will solve my problem but i don't know how to add a switch-if statement in a RecyclerView adapter.i am a beginer in android development so i need some help...
myadapter.java
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ImageViewHolder> {
#NonNull
private int[] images;
public RecyclerAdapter(int[] images){
this.images =images;
}
#Override
public ImageViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_item2,parent,false);
ImageViewHolder imageViewHolder = new ImageViewHolder(view);
return imageViewHolder;
}
#Override
public void onBindViewHolder(#NonNull ImageViewHolder holder, int position) {
int image_id =images[position];
holder.imagess.setImageResource(image_id);
}
#Override
public int getItemCount() {
return images.length;
}
public static class ImageViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
ImageView imagess;
TextView titless;
public ImageViewHolder(View itemView) {
super(itemView);
imagess = itemView.findViewById(R.id.image);
titless = itemView.findViewById(R.id.title);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View v) {
// Toast.makeText(itemView.getContext(), "DOWNLOAD ANY TORRENT DOWNLOADER AND OPEN", Toast.LENGTH_LONG).show();
}
}}
So what i want is :
I want to open different activities if the user click the cat image,it should open a activity named cats and if the user clicks the dog image it should open a activity named dogs ...
#Override
public void onClick(View v) {
switch(getAdapterPosition()) {
case 0:
Intent intent = new Intent(context, Cat.class);
context.startActivity(intent);
break;
case 1: // Open second activity
};
}
Returns the Adapter position of the item represented by this ViewHolder.
I have very little experience with Java, I write only in Kotlin. Here is what I have come up with.
Setting the click-events within the Adapter itself is not the best practise.According to the recommended way you should add a callback method and let the Activity
\ Fragment to which the Recycler is attached handle after the click events.
How to Proceed,
Step 1: Create an Interface which loosely binds your Adapter to Activity or Fragment.
interface AdapterListener{
void afterAdapterItemClicked(int adapterPosition);
}
This Interface can be created within Adapter itself as an inner member.
Step 2: Let the Activity or Fragment to which the Recycler is attached implement this Interface,So let assume your Activity is named as MenuActivity
class MenuActivity extends Activity implements AdapterListener{
}
Step 3: Now inside the Activity / Fragment implement the override method
#Override
void afterAdapterItemClicked(int adapterPosition){
switch(adapterPosition) {
case 0: // Move to activity1
break;
case 1: // Move to activity2
break;
}
}
Step 4 : Now calling the method afterAdapterClicked() after the click event
public static class ImageViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener {
ImageView imagess;
TextView titless;
public ImageViewHolder(View itemView) {
super(itemView);
imagess = itemView.findViewById(R.id.image);
titless = itemView.findViewById(R.id.title);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View v) {
mListener.afterAdapterItemClicked(getAdapterPosition());
}
}
Step 5: Now to all the curious faces thinking, where in the world mListener landed from, don't worry I saved it for the last.
Now when you create the RecyclerAdapter object(instance) inside your Activity / Fragment you need to pass the current context or this in its constructor.
RecyclerAdapter(arrayOfImages,this);
Now create a new state variable inside your RecyclerAdapter class such as
private AdapterListener mListener;
And then in the constructor of RecyclerAdapter you need to add a variable of type
AdapterListener like this and then assign mListener the received value
public RecyclerAdapter(int[] images,AdapterListener mListener){
this.images = images;
this.mListener = mListener;
}
And then use mListener inside your inner class ImageViewHolder.

Add item to RecyclerView dynamically

I want to be able to add items to my ReclycerView dynamically.
When an item loads -> setText() -> I add another item on list.
#Override
public void onBindViewHolder(ViewHolder holder, final int position) {
final Message message = mDataset.get(position);
if(message.isAnswers()) {
holder.mAnswer1Button.setText(message.getAnswer1());
holder.mAnswer2Button.setText(message.getAnswer2());
holder.mAnswer1Button.setOnClickListener(v -> {
if(message.getChild1() > 0) {
add(position + 1, dataListShared.get(message.getChild1()));
holder.mAnswer1Button.setClickable(false);
holder.mAnswer2Button.setEnabled(false);
}
});
} else {
holder.mMessageTextView.setText(message.getMessage());
if(message.getChild1() > 0) {
add(position + 1, dataListShared.get(message.getChild1()));
holder.mMessageTextView.setEnabled(false);
}
}
}
This is what I have inside onBindViewHolder. When I am on the first case if(), and I click the button, the item is added to the list. On the second Case else(), I would like for the text to be set on this current item and than already add another one.
How can I achieve this?
Moreover, why add() works inside onClickListener but not outside of it?
The error I get is:
java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling
Thanks! :)
The error itself is self explanatory ... It is dangerous to setOnClickListener in onBindViewHolder. This method is step of refresh each recycler item.
You should move method setOnClickListener to ViewHolder which is inner class on your adapter.
class MyViewHolder extends RecyclerView.ViewHolder
{
private final OnClickListener mOnClickListener = new MyOnClickListener();
Button mAnswer1Button, mAnswer2Button;
public MyViewHolder (View itemView) {
super(itemView);
mAnswer1Button = (Button) itemView.findViewById(R.id.item);
mAnswer2Button = (Button) itemView.findViewById(R.id.item);
mAnswer1Button.setOnClickListener(mOnClickListener);
}
#Override
public void onClick(final View view) {
//Now your Logic ....
}
}
One more thing you could do is set Create an interface OnItemClickListener
and declare onItemClick and then make the activity from where you are setting up the adapter for the particular recycler view implment OnItemClickListener this and over there you can dynamically add another item and setAdapter again or notifyDataSetChanged()
Your InterFace
public interface OnItemClickListener{
public void onItemClick(int position);
}
Your MainActivity
class MainActivity implements OnItemClickListener{
RecyclerView mRecyclerView ;
#Override
public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.layout);
mRecyclerView = (RecyclerView ) findViewById(R.id.recyclerview);
mAdapter= new MyAdapter(ArrayList, getContext(), MainActivity.this);
}
#Overrride
public void onItemClick(int position)
{
//Now your Logic ....
}
}
Now you can call this method onItemClick from the Adpater clas by setting onClickListener mAnswer1Button in the ViewHolder class and calling this method with in the onClick
As commented, I think it should work like this:
private int position;
#Override
public void onBindViewHolder(ViewHolder holder, final int position) {
this.position = position;
}
#Override
public void onViewAttachedToWindow(ViewHolder holder) {
final Message message = mDataset.get(position);
if(message.isAnswers()) {
holder.mAnswer1Button.setText(message.getAnswer1());
holder.mAnswer2Button.setText(message.getAnswer2());
holder.mAnswer1Button.setOnClickListener(v -> {
if(message.getChild1() > 0) {
add(position + 1, dataListShared.get(message.getChild1()));
holder.mAnswer1Button.setClickable(false);
holder.mAnswer2Button.setEnabled(false);
}
});
} else {
holder.mMessageTextView.setText(message.getMessage());
if(message.getChild1() > 0) {
add(position + 1, dataListShared.get(message.getChild1()));
holder.mMessageTextView.setEnabled(false);
}
}
}
In onBindViewHolder(), you apparently cannot change the dataset - I guess as the framework is still busy displaying the previous dataset. But when saving the position in an instance variable, and updating the dataset in onViewAttachedToWindow(), the RecylerView should be ready for more data.
To be honest though, I wouldn't add all this logic to the ViewHolder, but pass an interface to it so the logic can be kept in a more central place, like a presenter.

Open a new fragment and pass data when click on my Recycler CardView Grid?

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!

Add Spinner in recyclerview header and manage it's selection from It's fragment class

I am having a fragment in which I have recyclerview inside SwipeRefreshLayout.
Now I want to add a header to recyclerview that contains a spinner which I am able to do from RecyclerView Adapter, now the issue is I want to apply setOnItemSelectedListener on this spinner and perform some action on it, but I want this to process from the fragment class not from the adapter, but I am not able to do this.
Can you please guide me, how can I achieve this.
Thank you so much in advanced.
You can create a method in the Adapter class e.g.:
public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
// From my understanding you created the Spinner from within the Adapter.
// So it would live here.
public Spinner mySpinner;
#Override
public CustomFieldViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// Create ViewHolder.
}
#Override
public int getItemCount() {
// Return the number of itemViews in the RecyclerView here!
// Usually the size of the dataset being mapped to "rows" (itemViews).
}
#Override
public int getItemViewType(int position) {
// Map the current item in the dataset to a particular view.
}
#Override
public void onBindViewHolder(MyViewHolder viewHolder, int position) {
// Called when the ViewHolder is created / recycled.
}
public int getSpinnerSelection() {
// Just an example. You would have your custom method here.
return mySpinner.getSelectedItemPosition();
}
}
You can simply call the public method from your Fragment class where you will have an instance of you recycler's Adapter. Like so:
public class MyFragment extends android.support.v4.app.Fragment {
// ... Fragment methods.
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
RecyclerView myRecyclerView = (RecyclerView) getActivity().findViewById(R.id.my_recycler);
MyAdapter myAdapter = new new MyAdapter(/* things here for initialiser */);
myRecyclerView.setAdapter(myAdapter);
Button myButton = (Button) getActivity().findViewById(R.id.my_button);
TextView myTextView = (TextView) getActivity().findViewById(R.id.my_text_view);
myButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Use the reference to the adapter to call your custom method.
int numItemsInRecyclerView = myAdapter.getSpinnerSelection();
myTextView.setText("Number of items : " + numItemsInRecyclerView);
}
});
}
}

How should I initialize an array inside RecyclerView Adapter which has the size of a list while the list will not available on Adapter construction?

I have a RecyclerView that will contain list of item retrieved from the internet. So at first, the list will be empty. After the data retrieved from the internet, it will update the list and call notifyDataSetChanged().
I can adapt the data into the RecyclerView just fine. But, I have an ImageButton for each of item which has different Image if it's clicked. If I initialize the flags array inside onBindViewHolder, each time I scrolled the RecyclerView, the flag array will be reinitialize to false. If I initialize it in the Adapter constructor, it will be 0 index since the list will be empty at first. Where should I put array initializing in adapter if the data will come at some amount of time later?
Below is my code, but the flag array (isTrue) is always reinitialize each time I scrolled my RecyclerView.
public class SomethingAdapter extends RecyclerView.Adapter<SomethingAdapter.ViewHolder> {
private ArrayList<String> someList;
private boolean[] isTrue;
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView someText;
public ImageButton someButton;
public ViewHolder(View v) {
super(v);
someText = (TextView) v.findViewById(R.id.text);
someButton = (ImageButton) v.findViewById(R.id.button);
}
}
public SomethingAdapter(ArrayList<String> someList) {
this.someList = someList;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.some_layout, parent, false);
return new ViewHolder(v);
}
#Override
public void onBindViewHolder(final ViewHolder viewHolder, final int position) {
//TODO: This thing will make isTrue always reinitialize if scrolled
this.isTrue = new boolean[someList.getResults().size()];
viewHolder.someText.setText(someList.get(position));
if (isTrue[position]) {
viewHolder.someButton.setImageResource(R.drawable.button_true);
} else {
viewHolder.someButton.setImageResource(R.drawable.button_false);
}
viewHolder.someButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (isTrue[position]) {
//Connect to the internet and if response is positive {
//isTrue[position] = false;
//viewHolder.someButton.setImageResource(R.drawable.button_false);
//}
} else {
//Connect to the internet and if response is positive {
//isTrue[position] = true;
//viewHolder.someButton.setImageResource(R.drawable.button_true);
//}
}
}
});
}
#Override
public int getItemCount() {
return someList.size();
}
Initialize it when you add items to someList.
Also, don't add click listener in your onBind, create it in onCreateViewHolder. You cannot use position in the click callback, instead you should be using ViewHolder#getAdapterPosition.
See docs for details:
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#onBindViewHolder(VH, int)

Categories

Resources