-1

I have a recyclerview with a card and spinners in it, the values selected in the spinner I need to send it to the next activity to calculate estimated price. I have a button in the activity which is not of the recyclerview, that takes me to next activity where I need the values. So when I click this button I need it to collect the data and send it to next activity, how can I achieve that.

I have attached a picture. RED ARROW(data i need in next activity), BLUE ARROW(button press which takes me to next activity)

Screenshot of screen

I'm using this method to read the values

findMerchantTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                for (int i = 0; i < totalCards; i++) {
                    Spinner printColorSpinner = mRecyclerView.findViewHolderForLayoutPosition(i).itemView.findViewById(R.id.printColorSpinner);
                    Spinner printSidesSpinner = mRecyclerView.findViewHolderForAdapterPosition(i).itemView.findViewById(R.id.printSidesSpinner);
                    Spinner printSizeSpinner = mRecyclerView.findViewHolderForAdapterPosition(i).itemView.findViewById(R.id.paperSizeSpinner);
                    Spinner pagesSpinner = mRecyclerView.findViewHolderForAdapterPosition(i).itemView.findViewById(R.id.pagesSpinner);
                    Spinner orientationSpinner = mRecyclerView.findViewHolderForAdapterPosition(i).itemView.findViewById(R.id.orientationSpinner);
                    Spinner bindingTypeSpinner = mRecyclerView.findViewHolderForAdapterPosition(i).itemView.findViewById(R.id.bindingTypeSpinner);
                    MaterialTextView copiesTextView = mRecyclerView.findViewHolderForAdapterPosition(i).itemView.findViewById(R.id.copiesNumberText);
                    String color = printColorSpinner.getSelectedItem().toString().trim();
                    String sides = printSidesSpinner.getSelectedItem().toString().trim();
                    String size = printSizeSpinner.getSelectedItem().toString().trim();
                    String pages = pagesSpinner.getSelectedItem().toString().trim();
                    String orientation = orientationSpinner.getSelectedItem().toString().trim();
                    String bindingType = bindingTypeSpinner.getSelectedItem().toString().trim();
                    String totalCopies = copiesTextView.getText().toString().trim();
                }
            }
        });

But when i becomes 2, findViewHolderForLayoutPosition returns NullPointerException. I searched and got to know that the views are recycled and only the views that are laid out on screen will be readable.

What will be the alternative to get these values? Thanks!

Abhishek AN
  • 648
  • 7
  • 24

2 Answers2

0

The RecyclerView reuses the views to improve the performance and memory consumption and you only have access to the visible views. One way to accomplish your goal is to use an interface for the interactions between the Activity that contains the RecyclerView and the Adapter, and them update the main array of your objects.

Example:

  1. Imagine you have a Class to define your data.

YourClass.java

public class YourClass extends Object implements Parcelable {

  public String color = "";
  public String sides = "";
  public String size = "";
  public String pages = "";
  public String orientation = "";
  public String bindingType = "";
  public String totalCopies = "";

  //... your methods and implementations
}
  1. Lets define an interface to handle the events of the spinners

AdapterSpinnerListener.java

public interface AdapterSpinnerListener {
    public void onSpinnerChanged(String spinnerType, String spinnerValue,  int position);

    //... other events
}
  1. Implements a Custom Adapter to for your RecyclerView

CustomAdapter.java

public class CustomAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private AdapterActionListener adapterActionListener;

    // Your constructor receive the Fragment of Activity which implements the AdapterSpinnerListener.
    // Also receive the array of objects to populate the RecyclerView
    public CustomAdapter(YourActivity activity, ArrayList<YourClass> customRows)
    {
      this.adapterActionListener = activity;
    }

    //... implements all the needed functions for the RecyclerView
    //getItemId
    //getItemCount
    //getItemViewType
    //onCreateViewHolder

    //In your onBindViewHolder, you should add a listener for each one of your spinners. 
    //Also I'm supossing you have a CustomViewHolder to reference the Layout and Spinners.

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
      CustomViewHolder holder = (CustomViewHolder)viewHolder;
      holder.textViewSomething.setText(...);

      holder.printColorSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
          @Override
          public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
              if (adapterActionListener != null) {
                  // You can retrieve the selected item using parent.getItemAtPosition(pos)
                  adapterActionListener.onSpinnerChanged("color", selectedValue,  position);
              }
          }

          @Override
          public void onNothingSelected(AdapterView<?> adapterView) {

          }
      });

      //holder.printSidesSpinner.setOnItemSelectedListener...
      //...
      //...
      //... You should implement a listener for each spinner
    }
}
  1. In the Activity or Fragment that contains the RecyclerView you should implement the interface and define the array of your objects and populate the adapter with the data.

YourActivity.java

public class YourActivity extends AppCompatActivity implements AdapterSpinnerListener {
    private RecyclerView recyclerView;
    private CustomAdapter customAdapter;
    private ArrayList<YourClass> customRows;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.your_activity);

        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        customAdapter = new CustomAdapter(this, customRows);
        recyclerView.setAdapter(customAdapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(this.getActivity()));
    }

  // Implements the method of the interface
  @Override
  public void onSpinnerChanged(String spinnerType, String spinnerValue,  int position) {
    if (spinnerType.equals("color")) {
      customRows.get(position).color = spinnerValue;
    } else if (spinnerType.equals("sides")) {
      customRows.get(position).sides = spinnerValue;
    }
    //...
    //...
  }
}
  1. Now the values of your spinners are inside the customRows Array. You can retrieve the values in your click event
findMerchantTextView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            for (int i = 0; i < customRows.size(); i++) {
                String color = customRows.get(i).color;
                String sides = customRows.get(i).sides;
                //...
                //...
                //... all the values you want to get for the next activity
                //... 
            }
        }
    });
Ariel Perez
  • 499
  • 3
  • 7
  • Thanks a lot! What do I do about the totalCopies? That isn't a spinner, it is a textview. And will it be for (int i = 0; i < customRows.size(); i++) instead of just i – Abhishek AN May 31 '20 at 11:26
  • and why are we passing the position of value in spinner to customRows.get(position) ? – Abhishek AN May 31 '20 at 12:14
  • @AbhishekAN 1) is correct, inside the findMerchantTextView onClick i was missing the `size()` method and i was using the variable `position` instead of `i`. 2) We are passing the position value in two places: `onItemSelected` for trigger the interface function of which spinner do i changed (spinnerType), the spinnerValue and the `position` to references what row does that spinner belong. the other place is `onSpinnerChanged` where you receive the triggered event and update the correct customRows object. `customRows` hold a reference of the data for each displayed row in the RecyclerView. – Ariel Perez May 31 '20 at 13:12
  • About the textView: It's almost the same logic as the example that i posted. You should create some other events in the interface like `onTextChanged(String text, int position) `. Then on `onBindViewHolder ` you need to use `addTextChangedListener` to add a listener to your holder textView and trigger the event referencing the position inside the `onTextChanged `. After that you will be able to receive the text data for the specific position in your Activity. – Ariel Perez May 31 '20 at 13:27
  • adapterActionListener.onSpinnerChanged("color", selectedValue, pos); here position should be passed and not pos. Thanks a lot for your help! – Abhishek AN May 31 '20 at 20:50
  • @AbhishekAN correct. I already update the post. Your welcome. – Ariel Perez May 31 '20 at 21:31
  • Well I was wrong about passing `position`, as position value from `onBindViewHolder` is 0. The data from the 0th card can only be updated when a new row is added. Thus I moved the methods out of the `onBindViewHolder` to the `customViewHolder` like this retrieving position of card. – Abhishek AN May 31 '20 at 22:38
  • `int cardPosition = 0; if (adapterSpinnerListener != null) { if (listener != null) { cardPosition = getLayoutPosition(); } String selectedValue = parent.getItemAtPosition(pos).toString(); Log.e(TAG, "onItemSelected: " + selectedValue); adapterSpinnerListener.onSpinnerChanged("bindingtype", selectedValue, cardPosition); }` – Abhishek AN May 31 '20 at 22:38
0

Assuming one item is picked per spinner:

In the adapter of your RecyclerView:

class MyAdapter extends ...........{

//have these as member fields
public String color = "default";
public String sides = "default";
public String size = "default";
public String pages = "default";
public String orientation = "default";
public String bindingType = "default";
public String totalCopies = "default";
.......
.......


//when you interact with the spinners in onBindViewHolder

@Override
public void onBindViewHolder(........){

//on selecting color spinner item

holder.colorSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
    @Override
    public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {

      //set the color here  
      color = 
    }

    @Override
    public void onNothingSelected(AdapterView<?> parentView) {

    }

});


//on selecting sides spinner item

holder.sidesSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
    @Override
    public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {

      //set the sides here  
      sides = 
    }

    @Override
    public void onNothingSelected(AdapterView<?> parentView) {

    }

});

// you get the idea, do the rest here..........

}






}

In the activity that contains the Button that you want to click to send data:

class YourActivity extends .........{




//lets say you initialized the adapter like this

MyAdapter adapter = new MyAdapter(.....);

//when you click the button:


findMerchantTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

Intent intent = new Intent(YourActivity.this , TheOtherActivityName.class);
intent.putExtra("color" , adapter.color);
intent.putExtra("sides" , adapter.sides);
intent.putExtra("size" , adapter.size);
intent.putExtra("pages" , adapter.pages);
intent.putExtra("orientation" , adapter.orientation);
intent.putExtra("bindingType" , adapter.bindingType);
intent.putExtra("totalCopies" , adapter.totalCopies);
startActivity(intent);

}

});




}

In the activity that you want to read the data passed by the button:

class TheOtherActivityName extends .....{

//in onCreate()


String color = getIntent.getStringExtra("color");
String sides = getIntent.getStringExtra("sides");
String size = getIntent.getStringExtra("size");
........
........
........


}
Hasan Bou Taam
  • 4,017
  • 2
  • 12
  • 22