How to call a MainActivity method from ViewHolder in RecyclerView.Adapter?

Alexander Farber picture Alexander Farber · Sep 22, 2015 · Viewed 17.5k times · Source

In a simple app project at GitHub I have only 2 custom Java-files:

  1. MainActivity.java contains Bluetooth- and UI-related source code
  2. DeviceListAdapter.java contains an Adapter and ViewHolder for displaying Bluetooth devices in a RecyclerView

app screenshot

The MainActivity.java contains a method to be called, when user taps on a Bluetooth device in the RecyclerView:

public void confirmConnection(String address) {
    final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);

    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setMessage("Do you want to pair to " + device + "?");
    builder.setPositiveButton(R.string.button_ok, 
      new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
            device.createBond();
        }
    });
    builder.setNegativeButton(R.string.button_cancel, null);
    builder.show();
}

And in the ViewHolder class (in the DeviceListAdapter.java) the click listener is defined:

public class DeviceListAdapter extends
  RecyclerView.Adapter<DeviceListAdapter.ViewHolder> {

  private ArrayList<BluetoothDevice> mDevices = new ArrayList<BluetoothDevice>();

  protected static class ViewHolder
        extends RecyclerView.ViewHolder
        implements View.OnClickListener {

    private TextView deviceAddress;

    public ViewHolder(View v) {
        super(v);
        v.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        String address = deviceAddress.getText().toString();

        Toast.makeText(v.getContext(),
                "How to call MainActivity.confirmConnection(address)?",
                Toast.LENGTH_SHORT).show();
    }
  }

My problem:

How to call confirmConnection(address) method from ViewHolders onClick method?

I keep moving ViewHolder class declaration between the 2 Java files and also tried putting it into its own file - and just can't find the right way.

Should I maybe add a field to ViewHolder class and (when?) store a reference to MainActivity instance there?

UPDATE:

This works for me, but seems to be a workaround (and also I was thinking of using LocalBroadcastReceiver - which would be an even more hackish workaround) -

    @Override
    public void onClick(View v) {
        String address = deviceAddress.getText().toString();

        try {
            ((MainActivity) v.getContext()).confirmConnection(address);
        } catch (Exception e) {
            // ignore
        }
    }

Answer

Kevin Coppock picture Kevin Coppock · Sep 22, 2015

To keep your classes decoupled, I'd suggest defining an interface on your adapter, something like:

public interface OnBluetoothDeviceClickedListener {
    void onBluetoothDeviceClicked(String deviceAddress);
}

Then add a setter for this in your adapter:

private OnBluetoothDeviceClickedListener mBluetoothClickListener;

public void setOnBluetoothDeviceClickedListener(OnBluetoothDeviceClickedListener l) {
    mBluetoothClickListener = l;
}

Then internally, in your ViewHolder's onClick():

if (mBluetoothClickListener != null) {
    final String addresss = deviceAddress.getText().toString();
    mBluetoothClickListener.onBluetoothDeviceClicked(address);
}

Then just have your MainActivity pass in a listener to the Adapter:

mDeviceListAdapter.setOnBluetoothDeviceClickedListener(new OnBluetoothDeviceClickedListener() {
    @Override
    public void onBluetoothDeviceClicked(String deviceAddress) {
        confirmConnection(deviceAddress);
    }
});

This way you can reuse the adapter later without it being tied to that particular behavior.