ListFragment OnListItemClick not being called
Asked Answered
L

11

33

I have a class that extends ListFragment, and it overrides the OnListItemClick method. I am also doing this in another ListFragment the same way (and the method gets called). I am wondering why the method does not get called when I click on the list item?

Here is the code:

package org.doraz.fdboard.activity;

import java.sql.SQLException;
import java.util.Collection;

import org.doraz.fdboard.FantasyDraftBoardApplication;
import org.doraz.fdboard.R;
import org.doraz.fdboard.activity.DraftBoardActivity.PlayerDetailsActivity;
import org.doraz.fdboard.domain.FantasyLeague;
import org.doraz.fdboard.domain.FantasyTeam;
import org.doraz.fdboard.domain.Player;
import org.doraz.fdboard.domain.Position;
import org.doraz.fdboard.repository.FantasyDraftBoardRepository;
import org.doraz.fdboard.view.PlayerAdapter;
import org.doraz.fdboard.view.PlayerCursorAdapter;

import android.app.FragmentTransaction;
import android.app.ListFragment;
import android.app.ProgressDialog;
import android.content.Intent;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;

public class ListPlayersFragment extends ListFragment implements OnItemClickListener {

    private final static String TAG = "ListPlayersFragment";

    private boolean mDualPane;  
    private int curSelectedPlayerPosition = 0;
    private PlayerCursorAdapter playerAdapter;
    private QueryPlayersTask currentTask;

    private FantasyDraftBoardRepository repository;
    private FantasyLeague fantasyLeague;
    private FantasyTeam fantasyTeam;
    private Position position;
    private ProgressDialog progressDialog;


    /* (non-Javadoc)
     * @see android.app.ListFragment#onActivityCreated(android.os.Bundle)
     */
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        //Check  for the view players view along with the pane
        View teamListFragment = getActivity().findViewById(R.id.team_list_fragment);
        mDualPane = teamListFragment != null && teamListFragment.getVisibility() == View.VISIBLE;

        if(mDualPane) {
            Log.i(TAG, "Setting list select mode to single");
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        }

        getListView().setSmoothScrollbarEnabled(false);
    }

    /* (non-Javadoc)
     * @see android.app.ListFragment#onListItemClick(android.widget.ListView, android.view.View, int, long)
     */
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);
        Log.i(TAG, "[onListItemClick] Selected Position "+ position);
        selectPlayer(position);
    }

    /* (non-Javadoc)
     * @see android.app.Fragment#onSaveInstanceState(android.os.Bundle)
     */
    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("curSelectedPlayerPosition", curSelectedPlayerPosition);
        outState.putInt("fantasyLeague", fantasyLeague.getLeaguePID());

        if(!Position.ALL.equals(position)) {
            outState.putInt("position", position.getPositionPID());
        }

        if(!(FantasyTeam.ALL_AVAILABLE_TEAM.equals(fantasyTeam) || FantasyTeam.ALL_TEAM.equals(fantasyTeam))) {
            outState.putInt("fantasyTeam", fantasyTeam.getTeamPID());
        }
    }

    /**
     * Selects the player at this position in the current list
     * @param listPosition
     */
    public void selectPlayer(int listPosition) {
        curSelectedPlayerPosition = listPosition;

        Player player = (Player) playerAdapter.getItem(listPosition);

        Log.i(TAG, "Select Player ("+ listPosition +", "+ player.getName() +") called");

        //Get the player url
        String mPlayerUrl = player.getPlayerUrl();
        Log.d(TAG, "Selected Player URL: "+mPlayerUrl);

        if(mDualPane) {
            if(getListView().getSelectedItemPosition() == listPosition) {
                //Remove the selected item
                return;
            }

            //Select the item
            getListView().setItemChecked(listPosition, true);

            Log.d(TAG, "Creating ViewPlayerFragment");
            ViewPlayerFragment vpf = ViewPlayerFragment.newInstance(mPlayerUrl);
            ListTeamsFragment ltf = (ListTeamsFragment) getFragmentManager().findFragmentById(R.id.team_list_fragment);

            FragmentTransaction ft = getFragmentManager().beginTransaction();
            ft.replace(R.id.player_web_view_fragment, vpf);

            if(ltf != null && !ltf.isHidden()) {
                //Hide the list of teams
                ft.hide(ltf);
                ft.addToBackStack(null);
            }

            ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
            ft.commit();
            Log.d(TAG, "Committed to ViewPlayerFragment");
        }
        else {
            Log.d(TAG, "Launching new activity to view player");
            Intent intent = new Intent();
            intent.setClass(getActivity(), PlayerDetailsActivity.class);
            intent.putExtra("playerURL", mPlayerUrl);
            startActivityForResult(intent, 0);
        }
    }

    public void clearSelectedPlayer() {
        Log.i(TAG, "Clearing selected player");

        curSelectedPlayerPosition = -1;

        //Clear the list view
        getListView().clearChoices();

        ViewPlayerFragment vpf = (ViewPlayerFragment) getFragmentManager().findFragmentById(R.id.player_web_view_fragment);
        if(vpf != null) {
            Log.d(TAG, "Closing ViewPlayerFragment");

            //Close the ViewPlayersFragment
            FragmentTransaction ft = getFragmentManager().beginTransaction();
            ft.remove(vpf);
            ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
            ft.commit();
            Log.d(TAG, "Closed ViewPlayerFragment");
        }
    }

    /**
     * Initializes the player adapter
     */
    private void initializePlayerAdapter(Cursor cursor) {
        if(playerAdapter != null)
            return;

        playerAdapter = new PlayerCursorAdapter(getActivity(), cursor, (DraftBoardActivity)getActivity(), repository);
        setListAdapter(playerAdapter);
        setEmptyText(getText(R.string.no_players_msg));
    }

    /**
     * Initializes the player adapter
     */
    public void setPlayersCursor(Cursor cursor) {
        if(playerAdapter == null) {
            initializePlayerAdapter(cursor);
        }
        else {
            playerAdapter.changeCursor(cursor);
        }
    }

    /**
     * Drafts a player
     * 
     * @param mPlayer       the player to draft
     * @param fantasyTeam   the fantasy team
     * @param value         the draft value
     */
    public void draftPlayer(Player mPlayer, FantasyTeam fantasyTeam, Double value) {

        mPlayer.setFantasyTeam(fantasyTeam);
        mPlayer.setDraftValue(value);
        mPlayer.setDrafted(true);
        fantasyTeam.draftPlayer(mPlayer);

        try {
            repository.savePlayer(mPlayer);
            repository.saveFantasyTeam(fantasyTeam);
        } catch (SQLException e) {
            Log.e(TAG, "Error drafting player", e);
        }

        //Refresh the query
        refresh();
    }

    /**
     * Refreshes the players list
     */
    public void refresh(){
        if(fantasyLeague == null) {
            fantasyLeague = ((FantasyDraftBoardApplication) (getActivity().getApplication())).getCurrentFantasyLeague();
        }

        if(fantasyTeam == null) {
            fantasyTeam = FantasyTeam.ALL_AVAILABLE_TEAM;
        }

        if(position == null) {
            position = Position.ALL;
        }

        if(currentTask != null) {
            currentTask.cancel(true);
        }

        if(progressDialog != null) {
            progressDialog.dismiss();
            progressDialog = null;
        }

        progressDialog = ProgressDialog.show(getActivity(), null, "Loading...");
        currentTask = new QueryPlayersTask(fantasyLeague, fantasyTeam, position, repository);
        currentTask.execute();
    }

    /**
     * Sets the fantasyLeague
     * @param fantasyLeague the fantasyLeague to set
     */
    public void setFantasyLeague(FantasyLeague fantasyLeague) {
        this.fantasyLeague = fantasyLeague;
    }

    /**
     * Sets the fantasyTeam
     * @param fantasyTeam the fantasyTeam to set
     */
    public void setFantasyTeam(FantasyTeam fantasyTeam) {
        this.fantasyTeam = fantasyTeam;
    }

    /**
     * Sets the position
     * @param position the position to set
     */
    public void setPosition(Position position) {
        this.position = position;
    }

    /**
     * Sets the repository
     * @param repository the repository to set
     */
    public void setRepository(FantasyDraftBoardRepository repository) {
        this.repository = repository;
    }

    private class QueryPlayersTask extends AsyncTask<Integer, Integer, Cursor> {

        private FantasyLeague fantasyLeague;
        private FantasyTeam fantasyTeam;
        private Position position;
        private FantasyDraftBoardRepository repository;

        public QueryPlayersTask(FantasyLeague fantasyLeague, FantasyTeam fantasyTeam, Position position, FantasyDraftBoardRepository repository) {
            this.fantasyLeague = fantasyLeague;
            this.fantasyTeam = fantasyTeam;
            this.position = position;
            this.repository = repository;
        }

        @Override
        protected Cursor doInBackground(Integer... params) {
            try {
                return repository.queryForPlayersCursor(position, fantasyLeague, fantasyTeam);
            } catch (SQLException e) {
                Log.e("QueryPlayersTask", "Unable to query for players", e);
            }

            return null;
        }

        /* (non-Javadoc)
         * @see android.os.AsyncTask#onPostExecute(java.lang.Object)
         */
        @Override
        protected void onPostExecute(Cursor result) {
            super.onPostExecute(result);

            if(!isCancelled()) {
                //Update the player cursor
                updatePlayerCursor(result);
            }
        }
    }

    /**
     * Updates the player cursor
     * @param c the player cursor
     */
    private final void updatePlayerCursor(Cursor c){
        Log.d(TAG, "Updating player cursor.");
        if(playerAdapter == null)
            initializePlayerAdapter(c);
        else
            playerAdapter.changeCursor(c);

        if(progressDialog != null) {
            progressDialog.dismiss();
            progressDialog = null;
        }

        //Clear the current task
        currentTask = null;
    }

    @Override
    public void onItemClick(AdapterView<?> adapter, View arg1, int listPosition, long id) {
        Log.d(TAG, "[onItemClick] Clicked item "+position);
        selectPlayer(listPosition);
    }


}

Any assistance would be much appreciated. I can get the desired effect by implementing a few other listeners and assigning it to each list item, but I think this is the correct way to do it and it SHOULD work. I just don't know why it doesn't

Thanks in Advance.

Lainelainey answered 1/9, 2011 at 17:41 Comment(4)
I don't know the exact solution, but it's not necessary to implement OnItemClickListener. I think the onItemClick method is also redundant. I have a similar ListFragment implementation doing it like this and it works fine. You definitely don't see lines in your logcat?Ischia
Nope. No lines show up, and the items will not be selected. I am doing the same thing in my other ListFragment and it works fine. I only have the OnItemClickListener in there because the original method was not working.Lainelainey
Same problem here - implementing OnListItemClickListener instead of overriding the method does not work either, it's just not getting called.Photophobia
Solved this issue using the info from this other post: #1822371Clementius
I
98

If you have an item in your layout that can steal input from other components like a CheckBox, that component needs to be defined as not focusable.

Imco answered 9/8, 2012 at 14:48 Comment(7)
Absolutely right. This was my case. I had a checkbox and the clicks were not registering. I removed it and now works like a charm.Susan
This sounds like a bug? Why should a click on element X not trigger an event because there is an element Y on the same page which has focus? Anywhere we can report this?Sherrard
@Sherrard I don't think it is a bug but you can take your chances at code.google.com/p/android/issues/listImco
Instead of a CheckBox, I was using an ImageButton in my list cells. With that one, even setting focusable to false wasn't working. I had to make that control work as an ImageView instead, which was better anyway.Frederiksen
I would never have guessed that, thank you SO and thank you auselen.Laing
Great. EditText causes the same issue.Banshee
This is extremely non-intuitive, but probably has to do with the particular way that list items are selected, where there is one onListItemClick for the entire row regardless of which view within the row layout is actually clicked. Within that method you can then pick out the specific view if necessary.Imphal
F
16

Here is an elegant solution which allows onListItemClick to fire the way you think it should, and also allows any child views to receive events (or not, depending on how you want to set it.) For the question posed, this would allow for keeping the checkbox subview. Please take care to consider whether this is an intuitive experience for users.

On the root ViewGroup of all list items, set the descendantFocusability attribute in XML:

android:descendantFocusability="blocksDescendants"

Android SDK documentation of descendantFocusability. Has been part of the SDK since Android 1.0. Other settings for android:descendantFocusability include "beforeDescendants" and "afterDescendants".

Example cluster_layout.xml which sets descendantFocusability (List items applied to cluster_list.xml via an ArrayAdapter):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/cluster_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:background="#e0e0e0"
    android:clickable="false"
    android:descendantFocusability="blocksDescendants" >

    <FrameLayout
        android:id="@+id/cluster_sentiment_handle"
        android:layout_width="40dp"
        android:layout_height="match_parent"
        android:background="@color/handle_red" />
    <!-- other elements -->
</LinearLayout>

Example cluster_list.xml, which has no bearing on this solution other than to show that it's intended for use in a ListFragment by having a ListView element with the id @id/android:list:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="8dp"
    android:background="#f6f6f6" >
    <include layout="@layout/cluster_header" />
    <ListView
        android:id="@id/android:list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="top|center_horizontal"
        android:gravity="center_horizontal"
        android:divider="@android:color/transparent"
        android:dividerHeight="8dp"
        android:fastScrollEnabled="true" />
</LinearLayout>
Fonsie answered 22/11, 2013 at 20:44 Comment(3)
Champion lad!, I had many problems owing to a complex list view and this solution is an excellent cure all.Eliseo
Thanks! This solved my problem. I added android:descendantFocusability="blocksDescendants" to the parent layout of list item.Iguana
This was the solution I like the best. +1 for exposing a little known part of the API.Chericheria
J
14

I encountered the same problem after converting my app from a ListActivity to a ListFragment.

The ListFragment was the only fragment of the activity (no tabs, etc), but the ListFragment was not seeing any of the clicks.

The problem was that my Activity was still defined as a ListActivity. Changed it to a activity fixed it, and now the ListFragment sees the clicks.

    public class MyActivity extends ListActivity {
        @Override
        public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
            setContentView(R.layout.my_layout);
    }

should have been:

    public class MyActivity extends Activity {
        @Override
        public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
            setContentView(R.layout.my_layout);
    }



    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >        
        <fragment class="com.davidmarkscott.myapp.MyFragment"
        android:id="@+id/fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    </LinearLayout>
Jacinda answered 12/4, 2012 at 12:42 Comment(1)
This fixed my problem. Top bloke :DReclusion
T
4

Similar to others, I have a CustomAdapter with my ListFragment, which overrides getView() to provide the View for each row. Setting the listeners on the view which is returned worked for me. Example:

public class MyAdapter extends ArrayAdapter<SomeType> {

private Context context;
private List<SomeType> objects;

public MyAdapter(Context context, int resource, List<SomeType> objects) {
    super(context, resource, objects);
    this.context = context;
    this.objects = objects;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    LayoutInflater inflater = (LayoutInflater)context.getSystemService (Context.LAYOUT_INFLATER_SERVICE);
    View rowView = inflater.inflate(R.layout.list_subscription_item, null);

    TextView subTo = (TextView)rowView.findViewById(R.id.itemName);

    SomeType sub = objects.get(position);

    subTo.setText(sub.getName() + sub.getId());

    rowView.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            Toast.makeText(context, "from long click", Toast.LENGTH_SHORT).show();
            return false;
        }
    });

    rowView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Toast.makeText(context, "from click", Toast.LENGTH_SHORT).show();
        }
    });
    return rowView;
}
}
Tidings answered 15/11, 2013 at 11:59 Comment(0)
C
3

Only workaround so far, use a Fragment and a ListView then use setOnItemClickListener() which would make the ListFragment, and all its' convenience obsolete... After I created a new class containing exactly the same code (only the class name changed) and built the project on a different machine it "magically" worked (Still same API-Level). Below the code that worked. I also tried project->clean which usually fixes most problems, but that did not work eiter.

public class ContainerFragment extends ListFragment {

    private ContainerFragmentListener listener;

    public ContainerFragment(ContainerFragmentListener listener) {
        super();
        this.listener = listener;
    }
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        setListAdapter(new ContainerAdapter(getActivity()));
    }

     @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);
        Container container = (Container) getListAdapter().getItem(position);
        listener.containerSelected(container.id);
    }


}
Corroboree answered 14/11, 2011 at 19:44 Comment(0)
C
3

I also encountered the same problem. Using ListFragment which was inflated as a tab content of a tab inside a TabHost.

One of my TabHost has 3 tabs, the onListItemClick() was called correctly.

But the other TabHost has only 2 tabs, the onListItemClick() was not called.

So I decided to implement the work around that Marcel and H9kDroid mentioned above and it worked fine. Thanks for your answer.

Update: After messing around for a few hours, it seems that the problem related to the layout of the list item and the adapter of the ListView.

Inside my list item layout, there is one check box and the state of the check box is toggled inside the adapter. I think after the toggling, the ListView messed up and cannot deliver the call to onListItemClick().

I just changed the layout, remove the check box and it works fine.

Cheers.

Cockerham answered 19/3, 2012 at 3:3 Comment(0)
L
2

Echoing what @auselen said above, in my case I had android:textIsSelectable="true" set in a TextView, and it was stealing the callback.

Setting this to false solved the problem.

Lenka answered 9/5, 2013 at 6:35 Comment(0)
R
1

I found out that setting the property android:focusable to false on any child view of a list item prevents the click handler from being fired. The following setup works with a ListFragment.

navigation_fragment.xml:

<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/navigationEntries"
    android:layout_height="wrap_content"
    android:layout_width="fill_parent" />

navigation_entry.xml:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/navigationEntry"
            android:orientation="vertical"
            android:layout_height="wrap_content"
            android:layout_width="fill_parent"
            <!-- enhance this -->
            <ImageView
                    android:layout_width="fill_parent"
                    android:layout_height="1dp"
                    android:background="@android:color/darker_gray" />

Rightful answered 27/5, 2013 at 12:50 Comment(0)
B
1

I spent hours trying to figure out my own similar problem. I had a fairly simple compound text view that contained a checkbox and two textviews and a simple custom adapter. After trying various things that were suggested, I took out views until I just had one textview. Still didnt work. Do you know what did? I just deleted the xml layout file for the custom view I was using and made it again, piece by piece. Made the same exact thing with the same name. It just didn't like that file for some reason. So there you go. Some weird glitch in Eclipse I guess.

Bite answered 26/1, 2015 at 0:5 Comment(0)
B
1

I had called listfragment.getListView().setOnItemClickListener() and also overridden onListItemClick() on the ListFragment and that resulted in onListItemClick not being called. I deleted the setOnItemClickListener() method and the listFragment's onListItemClick method was called instead.

Batch answered 21/8, 2015 at 19:32 Comment(0)
Z
1

A CheckBox is focusable by default. This means that a click on a list item will be interpreted as toggling the CheckBox and will not reach your onListItemClick(…) method. This internal quirk of ListView means that any focusable widget that appears in a list item layout (like a CheckBox or a Button) should be made non-focusable to ensure that a click on a list item will work as you expect. Because your CheckBox only reports information and is not tied to any application logic, there is a simple solution. You can define the CheckBox as not focusable.

from The Big Nerd Ranch Guide book

Zarathustra answered 26/8, 2019 at 11:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.