Search code examples
androidlistviewandroid-adapteronitemclicklistener

Listview OnItemClickListener working on MainActivity but not on similar TaskActivity


In the MainActivity I have both a ContextMenu that responds to Long clicks and a regular OnItemClickListener that responds to regular clicks.

On the TaskActivity which is practically similar to the MainActivity, I also have a ContextMenu that responds to Long clicks, however when trying to set an OnItemClickListener, the items in the list view don't respond (they do respond to long clicks).

What am I missing? I tried various methods like changing clickable status to false and so on - none of them work. And that makes sense because I don't have them on the MainActivity XML's but it does work there.

MainActivity code:

public class MainActivity extends AppCompatActivity {

final Context context = this;
public static final int SIGN_IN = 1;
public static String currentTaskListId;
public static String currentUserId;
private TaskListAdapter mTaskListAdapter;
//TextView that is displayed when the list is empty//
private TextView mEmptyStateTextView;
//The loading indicator //
private View loadingIndicator;


// Firebase instance variables

private FirebaseAuth mFirebaseAuth;
private FirebaseAuth.AuthStateListener mAuthStateListener;
private FirebaseDatabase mFirebaseDatabase;
private DatabaseReference mTaskListDatabaseReference;
private ChildEventListener mChildEventListener;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Set the content of the activity to use the activity_main.xml layout file - the task lists
    setContentView(R.layout.activity_main);

    // Initialize Firebase components
    mFirebaseAuth = FirebaseAuth.getInstance();
    mFirebaseDatabase = FirebaseDatabase.getInstance();




    //Initialize firebase authentication
    mAuthStateListener = new FirebaseAuth.AuthStateListener() {
        @Override
        public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
            FirebaseUser user = firebaseAuth.getCurrentUser();
            if (user != null) {
                // user is signed in
                currentUserId=user.getUid();
                onSignedInInitialize(user.getUid());

            } else {
                // user is signed out
                onSignedOutCleanup();
                startActivityForResult(
                        AuthUI.getInstance()
                                .createSignInIntentBuilder()
                                .setIsSmartLockEnabled(false)
                                .setAvailableProviders(Arrays.asList(
                                        new AuthUI.IdpConfig.EmailBuilder().build(),
                                        new AuthUI.IdpConfig.GoogleBuilder().build()))
                                .setTosAndPrivacyPolicyUrls("https://superapp.example.com/terms-of-service.html",
                                        "https://superapp.example.com/privacy-policy.html")
                                .build(),
                        SIGN_IN);
            }
        }
    };


    //Initialize task list Array, ListView and Adapter.
    final ArrayList<TaskList> taskLists = new ArrayList<TaskList>();

    // Create an {@link TaskListAdapter}, whose data source is a list of {@link TaskList}s.
    mTaskListAdapter = new TaskListAdapter(this, taskLists);

    // Locate the {@link ListView} object in the view hierarchy of the {@link Activity}.
    ListView listView = (ListView) findViewById(R.id.task_list_view);

    //Set the empty view
    mEmptyStateTextView = (TextView) findViewById(R.id.empty_view);
    listView.setEmptyView(mEmptyStateTextView);

    //Initialize the loading indicator
    loadingIndicator = findViewById(R.id.loading_indicator);
    loadingIndicator.setVisibility(View.INVISIBLE);

    // Make the {@link ListView} use the {@link TaskListAdapter} defined above, so that the
    // {@link ListView} will display list items for each {@link TaskList} in the list.
    listView.setAdapter(mTaskListAdapter);

    //Set and create the FAB and it's action listener
    FloatingActionButton fab = findViewById(R.id.fab);
    fab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            // Get add_list.xml view
            LayoutInflater li = LayoutInflater.from(context);
            View addTaskListView = li.inflate(R.layout.add_list, null);

            //Create the prompt to enable the user to create a new task list
            AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
                    context);

            // Set add_list.xml as the layout for alertdialog builder
            alertDialogBuilder.setView(addTaskListView);

            //Set the user input box
            final EditText userInput = (EditText) addTaskListView
                    .findViewById(R.id.edit_list_name);

            // Set dialog message
            alertDialogBuilder
                    .setCancelable(false)
                    .setPositiveButton("Create",
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog,int id) {
                                    // Get list title from user and create a new task list
                                    //Also fetch the FireBase ID and connect it to the new task list.
                                    String mTaskListId = mTaskListDatabaseReference.push().getKey();
                                    TaskList taskList = new TaskList(userInput.getText().toString(),mTaskListId);
                                    mTaskListDatabaseReference.child(mTaskListId).setValue(taskList);
                                }
                            })
                    .setNegativeButton("Cancel",
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog,int id) {
                                    dialog.cancel();
                                }
                            });

            // Create the dialog
            AlertDialog alertDialog = alertDialogBuilder.create();

            // Show the dialog
            alertDialog.show();
        }
    });
    // Set an item click listener on the ListView, which creates an intent to open
    //the relevant task list and show the tasks inside.
    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
            // Find the current task list that was clicked on
            TaskList currentTaskList = mTaskListAdapter.getItem(position);

            //get the current task list's ID
            currentTaskListId=currentTaskList.getId();


            // Create a new intent to view the tasks in the chosen list
            Intent taskIntent = new Intent(MainActivity.this, TaskActivity.class);

            // Send the intent to launch a new activity
            startActivity(taskIntent);
        }
    });

    listView.setLongClickable(true);
    registerForContextMenu(listView);

}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == SIGN_IN) {
        if (resultCode == RESULT_OK) {
            // Sign-in succeeded, set up the UI
            Toast.makeText(this, "Signed in!", Toast.LENGTH_SHORT).show();
        } else if (resultCode == RESULT_CANCELED) {
            // Sign in was canceled by the user, finish the activity
            Toast.makeText(this, "Sign in canceled", Toast.LENGTH_SHORT).show();
            finish();
        }
    }
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.mini_menu,menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.sign_out:
            AuthUI.getInstance().signOut(this);
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }

}

@Override
protected void onResume() {
    super.onResume();
    mFirebaseAuth.addAuthStateListener(mAuthStateListener);
}
@Override
protected void onPause() {
    super.onPause();
    if (mAuthStateListener != null) {
        mFirebaseAuth.removeAuthStateListener(mAuthStateListener);
    }
    mTaskListAdapter.clear();
    detachDatabaseReadListener();

}

private void onSignedInInitialize(final String userId) {

    //Get reference for the task list for the logged in user and attach the database listener
    mTaskListDatabaseReference=mFirebaseDatabase.getReference().child("users").child(userId);
    loadingIndicator.setVisibility(View.VISIBLE);
    attachDatabaseReadListener();
    mEmptyStateTextView.setText("No task lists, add a new one!");
    loadingIndicator.setVisibility(View.GONE);


}

private void onSignedOutCleanup() {
    mTaskListAdapter.clear();
    detachDatabaseReadListener();
}
private void attachDatabaseReadListener() {
    if (mChildEventListener == null) {
        mChildEventListener = new ChildEventListener() {
            @Override
            public void onChildAdded(DataSnapshot dataSnapshot, String s) {
                TaskList taskList = dataSnapshot.getValue(TaskList.class);
                mTaskListAdapter.add(taskList);
            }
            public void onChildChanged(DataSnapshot dataSnapshot, String s) {}
            public void onChildRemoved(DataSnapshot dataSnapshot) {}
            public void onChildMoved(DataSnapshot dataSnapshot, String s) {}
            public void onCancelled(DatabaseError databaseError) {}
        };

    }
    mTaskListDatabaseReference.addChildEventListener(mChildEventListener);

}
private void detachDatabaseReadListener() {
    if (mChildEventListener != null) {
        mTaskListDatabaseReference.removeEventListener(mChildEventListener);
        mChildEventListener = null;
    }
}

public static String getCurrentTaskListId() {
    return currentTaskListId;
}
public static String getCurrentUserId() {
    return currentUserId;
}

/**
 * MENU
 */

@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo){
    if (v.getId() == R.id.task_list_view){
        AdapterView.AdapterContextMenuInfo info =(AdapterView.AdapterContextMenuInfo)menuInfo;
        menu.add(0,0,0,"Delete");
    }
}

@Override
public boolean onContextItemSelected(MenuItem menuItem){
    AdapterView.AdapterContextMenuInfo info=(AdapterView.AdapterContextMenuInfo)menuItem.getMenuInfo();
    TaskList taskListClicked=mTaskListAdapter.getItem(info.position);
    Log.d("check","" +taskListClicked.getTitle());
    switch (menuItem.getItemId()) {
        case 0:
            mTaskListDatabaseReference.child(taskListClicked.getId()).removeValue();
            mTaskListAdapter.remove(taskListClicked);
            Toast.makeText(this, "Task List deleted!", Toast.LENGTH_LONG).show();
            break;

        default:
            break;

    }
    return true;
}

}

TaskActivity code:

public class TaskActivity extends AppCompatActivity {
    final Context context = this;
    private TaskAdapter mTaskAdapter;
    private int taskCount;
    // TextView that is displayed when the list is empty //
    private TextView mEmptyStateTextView;
    //The loading indicator //
    private View loadingIndicator;
    //Edit text and button for creating new tasks quickly
    private EditText mTaskEditText;
    private Button mTaskCreateButton;

// Firebase instance variables
private FirebaseDatabase mFirebaseDatabase;
private DatabaseReference mTaskDatabaseReference;
private DatabaseReference mTaskNumDatabaseReference;
private ChildEventListener mChildEventListener;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Set the content of the activity to use the activity_main.xml layout file - the task lists
    setContentView(R.layout.task_activity);

    //Set up to allow Up navigation to parent activity
    this.getSupportActionBar().setDisplayHomeAsUpEnabled(true);

    // Initialize Firebase components
    mFirebaseDatabase = FirebaseDatabase.getInstance();

    // Initialize references to views
    mTaskEditText = (EditText) findViewById(R.id.task_edit_text);
    mTaskCreateButton = (Button) findViewById(R.id.create_task_button);

    // Enable Send button when there's text to send
    mTaskEditText.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            if (charSequence.toString().trim().length() > 0) {
                mTaskCreateButton.setEnabled(true);
            } else {
                mTaskCreateButton.setEnabled(false);
            }
        }

        @Override
        public void afterTextChanged(Editable editable) {
        }
    });

    // Create button creates a new task and clears the EditText
    mTaskCreateButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            // Get task title from user and create a new task
            //Also fetch the FireBase ID and connect it to the new task.
            //And finally get the task's creation date
            String creationDate ="Created: " + new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
            String taskId = mTaskDatabaseReference.push().getKey();
            Task task = new Task(mTaskEditText.getText().toString(),false,taskId,creationDate);
            mTaskDatabaseReference.child(taskId).setValue(task);

            //add that task to the list's task count
            mTaskNumDatabaseReference.child("taskNum").setValue(taskCount+1);



            // Clear input box
            mTaskEditText.setText("");
        }
    });


    //Initialize task Array, ListView and Adapter.
    final ArrayList<Task> tasks = new ArrayList<Task>();

    // Create an {@link TaskAdapter}, whose data source is a list of {@link Task}s.
    mTaskAdapter = new TaskAdapter(this, tasks);

    // Locate the {@link ListView} object in the view hierarchy of the {@link Activity}.
    ListView listView = (ListView) findViewById(R.id.task_list_view);

    //Set the empty view
    mEmptyStateTextView = (TextView) findViewById(R.id.empty_view);
    listView.setEmptyView(mEmptyStateTextView);

    //Initialize the loading indicator
    loadingIndicator = findViewById(R.id.loading_indicator);
    loadingIndicator.setVisibility(View.INVISIBLE);

    // Make the {@link ListView} use the {@link TaskAdapter} defined above, so that the
    // {@link ListView} will display list items for each {@link Task} in the list.
    listView.setAdapter(mTaskAdapter);

    //Set a regular click - opening the TaskInfoFragment
    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
            // Find the current task list that was clicked on
            Log.d("clicked here bro","clicikcckckc");
            Task currentTask = mTaskAdapter.getItem(position);

            //Open the TaskInfoFragment for this task
            TaskInfoFragment taskInfo = new TaskInfoFragment();
            taskInfo.setCurrentTask(currentTask);
            android.support.v4.app.FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            // Replace whatever is in the fragment_container view with this fragment,
            // and add the transaction to the back stack
            transaction.replace(R.id.frag_container, taskInfo);
            transaction.addToBackStack(null);

            // Commit the transaction
            transaction.commit();


        }
    });

    //Set context menu for ListView
    listView.setLongClickable(true);
    registerForContextMenu(listView);



    //Get reference for the task list for the logged in user and attach the database listener
    mTaskDatabaseReference=mFirebaseDatabase.getReference().child("users")
            .child(MainActivity.getCurrentUserId())
            .child(MainActivity.getCurrentTaskListId()).child("tasks");
    mTaskNumDatabaseReference=mFirebaseDatabase.getReference().child("users")
            .child(MainActivity.getCurrentUserId())
            .child(MainActivity.getCurrentTaskListId());


    //add listener to get the current task count in this specific task list
    mTaskNumDatabaseReference.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            TaskList taskList = dataSnapshot.getValue(TaskList.class);
            taskCount=taskList.getTaskNum();
            Log.d("post count: ", "" + taskCount);
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {
            System.out.println("The read failed: " + databaseError.getCode());
        }
    });
}

@Override
protected void onResume() {
    super.onResume();
    loadingIndicator.setVisibility(View.VISIBLE);
    attachDatabaseReadListener();
    mEmptyStateTextView.setText("No tasks, add a new one!");
    loadingIndicator.setVisibility(View.GONE);

}
@Override
protected void onPause() {
    super.onPause();

    mTaskAdapter.clear();
    detachDatabaseReadListener();

}

private void attachDatabaseReadListener() {
    if (mChildEventListener == null) {
        mChildEventListener = new ChildEventListener() {
            @Override
            public void onChildAdded(DataSnapshot dataSnapshot, String s) {
                Task task = dataSnapshot.getValue(Task.class);
                mTaskAdapter.add(task);
            }
            public void onChildChanged(DataSnapshot dataSnapshot, String s) {
                //Task task = dataSnapshot.getValue(Task.class);
                //mTaskAdapter.add(task);
            }
            public void onChildRemoved(DataSnapshot dataSnapshot) {
                mTaskNumDatabaseReference.child("taskNum").setValue(taskCount-1);
            }
            public void onChildMoved(DataSnapshot dataSnapshot, String s) {}
            public void onCancelled(DatabaseError databaseError) {}
        };

    }
    mTaskDatabaseReference.addChildEventListener(mChildEventListener);

}
private void detachDatabaseReadListener() {
    if (mChildEventListener != null) {
        mTaskDatabaseReference.removeEventListener(mChildEventListener);
        mChildEventListener = null;
    }
}

/**
 * MENU
 */

@Override
public void onCreateContextMenu(ContextMenu menu,View v, ContextMenu.ContextMenuInfo menuInfo){
    if (v.getId() == R.id.task_list_view){
        AdapterView.AdapterContextMenuInfo info =(AdapterView.AdapterContextMenuInfo)menuInfo;
        menu.add(0,0,0,"Delete");
        menu.add(0,1,1,"info");
    }
}

@Override
public boolean onContextItemSelected(MenuItem menuItem){
    AdapterView.AdapterContextMenuInfo info=(AdapterView.AdapterContextMenuInfo)menuItem.getMenuInfo();
    Task taskClicked=mTaskAdapter.getItem(info.position);
    Log.d("check","" +taskClicked.getTitle());
    switch (menuItem.getItemId()) {
        case 0:
            mTaskDatabaseReference.child(taskClicked.getId()).removeValue();
            mTaskAdapter.remove(taskClicked);
            Toast.makeText(this, "Task deleted!", Toast.LENGTH_LONG).show();
            break;

        case 1:
            //Open the TaskInfoFragment for this task
            TaskInfoFragment taskInfo = new TaskInfoFragment();
            taskInfo.setCurrentTask(taskClicked);
            android.support.v4.app.FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            // Replace whatever is in the fragment_container view with this fragment,
            // and add the transaction to the back stack
            transaction.replace(R.id.frag_container, taskInfo);
            transaction.addToBackStack(null);

            // Commit the transaction
            transaction.commit();
            break;

        default:
            break;

    }
    return true;
}


//set up the back button - to navigate to the parent activity
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        // Respond to the action bar's Up/Home button
        case android.R.id.home:
            //Check if the call came from the TaskInfoFragment or the activity
            Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.frag_container);
            if(currentFragment!=null && currentFragment.isVisible()){
                this.onBackPressed();
            }
            else{
                NavUtils.navigateUpFromSameTask(this);

            }
            return true;

    }
    return super.onOptionsItemSelected(item);
}

}

TaskAdapter - the TaskActivity Adapter

    package com.example.guyerez.todotiger;

import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;

import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;

import java.util.ArrayList;
import java.util.Locale;

/**
 * {@link TaskAdapter} is an {@link ArrayAdapter} that can provide the layout for each task item
 * based on a data source, which is a list of {@link Task} objects.
 */

public class TaskAdapter extends ArrayAdapter<Task> {

//Define FireBase instance variables
private DatabaseReference mTaskDatabaseReference;
private FirebaseDatabase mFirebaseDatabase;
/**
 * Create a new {@link TaskAdapter} object.
 *
 * @param context is the current context (i.e. Activity) that the adapter is being created in.
 * @param tasks is the list of {@link Task}s to be displayed.
 */


public TaskAdapter(Context context, ArrayList<Task> tasks) {
    super(context, 0, tasks);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    // Check if an existing view is being reused, otherwise inflate the view
    View listItemView = convertView;
    if (listItemView == null) {
        listItemView = LayoutInflater.from(getContext()).inflate(
                R.layout.task_item, parent, false);
    }
    // Get the {@link Task} object located at this position in the list
    final Task currentTask = getItem(position);

    // Locate the TextView in the task_item.xml layout with the ID task_title.
    final TextView titleTextView = (TextView) listItemView.findViewById(R.id.task_title);
    // Get the task's title from the currentTask object and set it in the text view
    titleTextView.setText(currentTask.getTitle());
    //If the task is completed - title Strikethrough
    titleTextView.setBackgroundResource(strikeCompleted(currentTask.getCompleted()));

    //Initialize the check box and check it if the task was completed.
    CheckBox checkBox = (CheckBox) listItemView.findViewById(R.id.check_box);
    checkBox.setOnCheckedChangeListener(null);
    checkBox.setChecked(currentTask.getCompleted());

    //Initialize the creation date TextView in the task_item.xml layout with the ID creation_date
    TextView creationDateTextView = (TextView) listItemView.findViewById(R.id.creation_date);
    //Get the task's creation date from the currentTask object and set it in the text view
    creationDateTextView.setText(currentTask.getCreationDate());

    // Initialize Firebase DB
    mFirebaseDatabase = FirebaseDatabase.getInstance();
    //Get the task DB reference to edit task completion status

    // Find the CheckBox in the task_item.xml layout with the ID check_box.

    checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView,boolean isChecked) {
            mTaskDatabaseReference=mFirebaseDatabase.getReference()
                    .child("users").child(MainActivity.getCurrentUserId())
                    .child(MainActivity.getCurrentTaskListId()).child("tasks").child(currentTask.getId());
                if (isChecked) {
                    titleTextView.setBackgroundResource(R.drawable.strike_through);
                    mTaskDatabaseReference.child("completed").setValue(true);
                } else {
                    titleTextView.setBackgroundResource(0);
                    mTaskDatabaseReference.child("completed").setValue(false);
                }

        }
        }
    );
    // Return the whole list item layout (containing 1 text view and 1 checkbox) so that it can be shown in the ListView.
    return listItemView;
}
private int strikeCompleted(boolean completed){
    if (completed){
        return R.drawable.strike_through;
    }
    else{
        return 0;
    }
}

}


Solution

  • After trying the several recommended workarounds (like here) for ListView rows containing a CheckBox without any success, I'd like to suggest a different approach:

    Use another, transparent View which covers the whole row except for a small area around the CheckBox. Let this View have an OnClickListener which triggers the opening of the TaskInfoFragment.

    Ideally one would use an interface so the TaskAdapter could pass the clicked Task to the TaskActivity which in turn would show the Fragment.

    Several hours later... (@Guy, your hint that you "tried deleting all checkbox related code and xml, and the regular click problem persisted" made me exchange one component after another until I had it narrowed down to the row xml) I found the reason why the ListView's OnItemClickListener in TaskActivity does not fire. The root ConstraintLayout of the Task list has an attribute which the root of the TaskList list does not have: android:longClickable="true"

    Removing this attribute makes the ListView behave normally.