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;
}
}
}
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.