3

so, before latest update, I use onListItemClick listener and it works fine, but now I tried to use RecyclerView, and I'm not sure how to implement onClick for each item, that will open up a new activity..

this is what I used to have

public class SermonsFragment extends Fragment {

    @Override
    public void onListItemClick(ListView list, View v, int position, long id) {
        Intent mediaStreamIntent = new Intent(getActivity(), MediaStreamPlayer.class);
        mediaStreamIntent.putExtra("sermon_details", (android.os.Parcelable) list.getItemAtPosition(position));
        startActivity(mediaStreamIntent);
    }
}

but now, instead of using listview I create a sermon adapter and it looks like this

public class SermonListAdapter extends RecyclerView.Adapter<SermonListAdapter.ViewHolder>{
    private ArrayList<Sermon> mDataset;

    // Provide a reference to the views for each data item
    // Complex data items may need more than one view per item, and
    // you provide access to all the views for a data item in a view holder
    //Note: need to remove static class no idea why
    public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
        // each data item is just a string in this case
        public View mView;
        public ViewHolder(View v) {
            super(v);
            v.setOnClickListener(this);
            mView = v;
        }

        @Override
        public void onClick(View v) {
            Log.d("SermonsListAdapter.java.debug", "itemClick " + mDataset.get(getPosition()).getName());

        }
    }

    // Provide a suitable constructor (depends on the kind of dataset)
    public SermonListAdapter(ArrayList<Sermon> myDataset) {
        mDataset = myDataset;
    }

    // Create new views (invoked by the layout manager)
    @Override
    public SermonListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                   int viewType) {
        // create a new view
        View v = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.sermon_cardview, parent, false);
        // set the view's size, margins, paddings and layout parameters

        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        // - get element from your dataset at this position
        // - replace the contents of the view with that element
        TextView title = (TextView) holder.mView.findViewById(R.id.sermon_title);
        TextView series = (TextView) holder.mView.findViewById(R.id.sermon_series);
        TextView pastor = (TextView) holder.mView.findViewById(R.id.sermon_pastor);
        TextView sermonDate = (TextView) holder.mView.findViewById(R.id.sermon_date);

        title.setText(mDataset.get(position).getName());
        series.setText(mDataset.get(position).getSeries());
        pastor.setText(mDataset.get(position).getPastor());
        sermonDate.setText(mDataset.get(position).getSermonDate());

    }

and the fragment is more or less the same, it's just I can't use onListItemClick anymore

    public class SermonsFragment extends Fragment {
        private static final int MAX_SERMONS_LIST = 20;
        private ArrayAdapter<Sermon> listAdapter;
        private String imageUrl;
        private static String sermonListJSONUrl = “http://someurl”;

        private RecyclerView mRecyclerView;
        private RecyclerView.Adapter mAdapter;
        private RecyclerView.LayoutManager mLayoutManager;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setRetainInstance(true);

            //Check if there is internet, if yes call JSONParser
            ConnectionDetector myConnection = new ConnectionDetector(getActivity().getApplicationContext());
            Boolean isInternetOnline = false;
            isInternetOnline = myConnection.isConnectingToInternet();

            if(isInternetOnline) {
                //Call JSONParser Asynchronously to get sermonList in JSON Format
                new callJSONParserAsync().execute(sermonListJSONUrl);
            }
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_sermons, container, false);
            mRecyclerView = (RecyclerView) rootView.findViewById(R.id.my_recycler_view);
            return rootView;
        }

        @Override
        public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);

            mLayoutManager = new LinearLayoutManager(getActivity());
            mRecyclerView.setLayoutManager(mLayoutManager);
            mRecyclerView.setItemAnimator(new DefaultItemAnimator());

            //Just an Empty Class
            ArrayList<Sermon> mySermon = new ArrayList<Sermon>();

            //specify an adapter
            mAdapter = new SermonListAdapter(mySermon);
            mRecyclerView.setAdapter(mAdapter);
        }

I have the cardview xml look like this

<!-- A CardView that contains a TextView -->
<android.support.v7.widget.CardView
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:id="@+id/card_view"
    android:layout_margin="5dp"
    android:layout_width="match_parent"
    android:layout_height="100dp"
    card_view:cardCornerRadius="1dp">

    <LinearLayout android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:id="@+id/sermon_title" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:id="@+id/sermon_series" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:id="@+id/sermon_pastor" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:id="@+id/sermon_date" />
    </LinearLayout>
</android.support.v7.widget.CardView>

I've got this error when try to create new intent

12-18 22:31:48.469  31887-31887/org.ifgfseattle.ifgfseattle E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: org.ifgfseattle.ifgfseattle, PID: 31887
    android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity  context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
            at android.app.ContextImpl.startActivity(ContextImpl.java:1232)
            at android.app.ContextImpl.startActivity(ContextImpl.java:1219)
            at android.content.ContextWrapper.startActivity(ContextWrapper.java:322)
            at org.ifgfseattle.ifgfseattle.adapter.SermonListAdapter$1.onClick(SermonListAdapter.java:81)
            at android.view.View.performClick(View.java:4756)
            at android.view.View$PerformClick.run(View.java:19749)
            at android.os.Handler.handleCallback(Handler.java:739)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:135)
            at android.app.ActivityThread.main(ActivityThread.java:5221)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
Harts
  • 3,757
  • 9
  • 45
  • 83

1 Answers1

15

You could implement an onClick on the view in the onBindViewHolder method of yours inside the adpater.

  1. Assign an id to the view that holds the item cell
  2. Get the view just the way you have for the textviews
  3. set an onClick to the root inside the method like this:

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        viewHolder.relLayout.setOnClickListener(new OnClickListener(){
            public void onClick(View v) {
                 // perform your operations here 
            }
        });
    }
    

EDIT:

This is how you assign an id in the xml

<LinearLayout android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:id="@+id/lnrLayout"  ---------->> This is new
    android:orientation="vertical">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:id="@+id/sermon_title" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:id="@+id/sermon_series" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:id="@+id/sermon_pastor" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:id="@+id/sermon_date" />
</LinearLayout>

This is how you define the views (or probably instantiate them)

public ViewHolder(View mView) {
        super(view);
        title = (TextView) holder.mView.findViewById(R.id.sermon_title);
        series = (TextView) holder.mView.findViewById(R.id.sermon_series);
        pastor = (TextView) holder.mView.findViewById(R.id.sermon_pastor);
        sermonDate = (TextView) holder.mView.findViewById(R.id.sermon_date)
        lnrLayout = (LinearLayout)holder.mView.findViewById(R.id.lnrLayout);
    }

That's your custom viewholder, so declare the TextViews just the way we declare variables.. your onBindView method wil therefore look like this now:

 @Override
public void onBindViewHolder(ViewHolder holder, int position) {
    holder.title.setText(mDataset.get(position).getName());
    holder.series.setText(mDataset.get(position).getSeries());
    holder.pastor.setText(mDataset.get(position).getPastor());
    holder.sermonDate.setText(mDataset.get(position).getSermonDate());
    holder.lnrLayout.setOnClickListener(new OnClickListener(){
          public void onClick(View v) {
             // on click action here
             //-- use context to start the new Activity
            Intent mediaStreamIntent = new Intent(mContext, MediaStreamPlayer.class);
            mediaStreamIntent.putExtra("sermon_details", (android.os.Parcelable) mDataset.get(position));
            mContext.startActivity(mediaStreamIntent);
          }
    });
}

I really have no idea why there is difference between the two, may be its because you are intializing the views inside onbind instead of the viewholder constructor.

You could also refer to this

EDIT 2: (2nd method)

Change you adapter to the following:

// Provide a suitable constructor (depends on the kind of dataset)
public SermonListAdapter(ArrayList<Sermon> myDataset, Fragment fragment) {
    mDataset = myDataset;
    mFragment = fragment;
}

In the onClick do this:

if(mFragment != null && mFragment instanceof SermonFragment) {
      ((SermonFragment)mFragment).sendToNextActivity(position); -> you can pass any data you wsh to
 }

In the fragment class create a public method with the name sendToNextAcitivity with the same param definition and then call the next intent.

3rd method

Create an interface in the adapter, create a set method for the interface, implement the interface in the fragment and then initialize it, and then pass it to the set method of the adapter.

then use this:

if(mListener!= null) {
         mListener.sendToNextActivity(position); -> you can pass any data you wsh to
     }
Community
  • 1
  • 1
  • Several question here :is there any difference between implement click in onBindViewHolder vs public class ViewHolder extends RecyclerView.ViewHolder? as you can see right now I have it in ViewHolder class, and when I tried to create intent it give syntax error. also how do you assign an id to the view? really beginner here, need step by step tutorial.. Thank you – Harts Dec 19 '14 at 04:37
  • @Harts - also refer to the link if it helps – Rat-a-tat-a-tat Ratatouille Dec 19 '14 at 04:55
  • 1
    so, I've got it this far, lnrLayoutCardView.setOnClickListener(new View.OnClickListener(){ public void onClick(View v) { // on click action here Log.d("SermonsListAdapter.java.debug", "Test"); Intent mediaStreamIntent = new Intent(getActivity(), MediaStreamPlayer.class); mediaStreamIntent.putExtra("sermon_details", (android.os.Parcelable) mDataset.get(position)); startActivity(mediaStreamIntent); } }); I can't use getactivity and start activity,it said cannot resolve method – Harts Dec 19 '14 at 05:40
  • well I can use variable position by making it final at public void onBindViewHolder(ViewHolder holder, final int position). So I just need to create a new intent or activity – Harts Dec 19 '14 at 05:46
  • 2
    instead of getActivity you need to you the context.. that u can get in the constructor of the adapter.. so then store it in a local context obj.. and use that one. – Rat-a-tat-a-tat Ratatouille Dec 19 '14 at 05:56
  • so I get it this far: Intent mediaStreamIntent = new Intent((MediaStreamPlayer)mContext, MediaStreamPlayer.class); mediaStreamIntent.putExtra("sermon_details", (android.os.Parcelable) mDataset.get(position)); startActivity(mediaStreamIntent);, but now the startActivity is the problem.. how can I start the new activity? – Harts Dec 19 '14 at 06:03
  • ah yes, I did tried that, but it cause error.. refer to the question, I will put it there – Harts Dec 19 '14 at 06:16
  • is is possible I do it wrong when passing the context like this at SermonsFragment.java : mAdapter = new SermonListAdapter(mySermon, getActivity().getApplicationContext()); – Harts Dec 19 '14 at 06:21
  • nevermind.. I got it work by adding mediaStreamIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); Not sure if this is best practice. let me know if I should do it some other way. Thank you for the help – Harts Dec 19 '14 at 06:36
  • 1
    flag_new_task basically creates the new activity as a fresh activity in the stack..so pressing the back button will not lead you to the previous screen , but out of the app , if that the only screen(nt sure).. the other way u cud do is, pass a reference to the fragment in the adapter, and then pass the position to the fragment class, nd then start a new activity from the fragment.. wait @Harts check the edit.. – Rat-a-tat-a-tat Ratatouille Dec 19 '14 at 06:39
  • Thanks for the help... so far, the flag did not cause problem, and still go back to the fragment which start the activity. The 2nd and 3rd method certainly will help. – Harts Dec 19 '14 at 06:54
  • A better idea: Instead of putting onClick on lnrLayout, put it on viewHolder.itemView, which is the parent. Two benefits: You can change the list item view without changing java code, and, more importantly, you ensure you can click everywhere on the list item. – carl Apr 21 '21 at 10:42