My app uses a firebase RecyclerAdapter to display "plant" entities in an activity.
I made it so that if I longclick on an entity displayed in Cardview format using a ViewHolder and the RecyclerAdapter it will prompt to perform a delete and delete the plant entity + image on the firebase database and storage. Deleting and adding plants work without a hitch on the backend as I can confirm this on my firebase database.
However, when I delete a "plant" entity and add a new one, the CardView shows the previous or another image. Deleting the app and reinstalling it seems to fix the problem, because of this I think it might have to do with the local cache.
PlantActivity.java (where the plants are loaded)
I think this might be solved if there was some way to refresh the activity in some way or the RecyclerAdapter... although i've tried many things already
public class PlantActivity extends AppCompatActivity {
private static final int ADD_REQUEST = 101;
private static final String TAG = "PlantActivityView";
private DatabaseReference mDatabaseReference;
private FirebaseRecyclerAdapter plantAdapter;
private PlantDAO mPlantDAO;
private UserDAO mUserDAO;
private CoordinatorLayout coordinatorLayout;
#Override
protected void onStart() {
super.onStart();
if(!mUserDAO.isLoggedIn()){
finish();
startActivity(new Intent(PlantActivity.this, LoginActivity.class));
}
plantAdapter.startListening();
}
#Override
protected void onStop() {
super.onStop();
plantAdapter.stopListening();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_plant_view);
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
mDatabaseReference = FirebaseDatabase.getInstance().getReference();
mPlantDAO = new PlantDAO(mDatabaseReference);
mUserDAO = new UserDAO();
//make custom appcompat toolbar to replace actionbar and add logout item
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
coordinatorLayout = findViewById(R.id.plant_coordinator);
providePlantsOfCurrentUser();
findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(),PlantAddActivity.class);
startActivityForResult(intent, ADD_REQUEST);
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.menuLogout:
mUserDAO.getAuth().signOut();
finish();
startActivity(new Intent(PlantActivity.this,LoginActivity.class));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Snackbar GoodResultSnackbar = Snackbar.make(coordinatorLayout,"PLANT ADDED",Snackbar.LENGTH_SHORT);
Snackbar BadResultSnackbar = Snackbar.make(coordinatorLayout,"PLANT ADD FAILED",Snackbar.LENGTH_SHORT);
if(requestCode == ADD_REQUEST){
if(resultCode == Activity.RESULT_OK){
GoodResultSnackbar.show();
} else if(resultCode == Activity.RESULT_CANCELED){
BadResultSnackbar.show();
}
}
super.onActivityResult(requestCode, resultCode, data);
}
public void providePlantsOfCurrentUser(){
FirebaseRecyclerOptions<Plant> options = new FirebaseRecyclerOptions.Builder<Plant>().setQuery(mPlantDAO.currentUserPlantsQuery(), Plant.class).build();
plantAdapter = new FirebaseRecyclerAdapter<Plant,PlantViewHolder>(options) {
#NonNull
#Override
public PlantViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.cardview_item_plant, parent, false);
return new PlantViewHolder(view);
}
#Override
protected void onBindViewHolder(#NonNull final PlantViewHolder holder, final int position, #NonNull final Plant model) {
StorageReference localstorage = FirebaseStorage.getInstance().getReferenceFromUrl(model.getImageLocation());
String plantText = /*model.getPlantID() + ": " + */ model.getPlanttype();
holder.tv_plant_name.setText(plantText);
//image implementation
GlideApp.with(getApplicationContext()).load(localstorage).into(holder.img_plant_thumbnail);
holder.dialogClickListener = new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
switch (which){
case DialogInterface.BUTTON_POSITIVE:
mPlantDAO.deletePlant(model.getPlantID());
plantAdapter.notifyDataSetChanged();
break;
case DialogInterface.BUTTON_NEGATIVE:
//Return
break;
}
}
};
holder.cardView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//passing data with intent to PlantDetailActivity instance
Intent intent = new Intent(getApplicationContext(), PlantDetailActivity.class);
intent.putExtra("plantID", model.getPlantID());
intent.putExtra("planttype", model.getPlanttype());
//image implementation
intent.putExtra("image_url", model.getImageLocation());
//start the activity
startActivity(intent);
}
});
holder.cardView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
showDeleteDialog(holder);
return true;
}
});
}
};
RecyclerView recyclerView = findViewById(R.id.recyclerview_id);
recyclerView.setAdapter(plantAdapter);
recyclerView.setLayoutManager(new GridLayoutManager(this, 3));
}
private void showDeleteDialog(PlantViewHolder holder){
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Delete this plant?").setPositiveButton("Yes", holder.dialogClickListener)
.setNegativeButton("Cancel", holder.dialogClickListener).show();
}
}
The position and model are final on the onBindViewHolder method
The viewholder can be final, but not the data or the position
Use holder.getAdapterPosition() to get the position inside annonymous interfaces like click listeners
And then get the data using getItem() method from the adapter class
The last answer by #cutiko definitely helped! But I think I realize the problem. I was saving my plantID string attribute (of new Plant object) in this fashion:
"plant_" + count() of "plants" in database + "_" + ID of the current user + ".jpg"
The problem this causes is that when I delete a plant entity and add another this could cause 2 plant entities in the database to have the same ID which causes strange behavior.
I will solve this by instead of deleting a plant from the database, changing a new attribute's("active") to false and not showing these in the FirebaseRecyclerAdapter
Related
How do I write Shared-preference Code that is using Firebase for User authentication and the data that is saved by a user is only accessible for them?
My current application Saves a note and displays it which is the main thing needed in my project but when a user logs out and new user logs in he/she can also view the data so the data is not private. My sir suggested to make nodes for users using shared preference but i couldn't find any solutions
HomeActivity:
public class HomeActivity extends AppCompatActivity {
EditText Descriptionholder;
Button Savebtn;
DatabaseReference DatabaseNote;
ListView listViewNotes;
List<PrivateNote> privateNoteList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
DatabaseNote = FirebaseDatabase.getInstance().getReference("privatenote");
Descriptionholder = findViewById(R.id.description2);
Savebtn = findViewById(R.id.buttonsave);
listViewNotes= findViewById(R.id.listViewPrivate);
privateNoteList = new ArrayList<>();
Savebtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
addnote();
}
});
listViewNotes.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
PrivateNote privateNote = privateNoteList.get(i); //confusion
showUpdateDialog(privateNote.getNoteId(),privateNote.getNoteDescription());
return false;
}
});
}
public boolean onCreateOptionsMenu (Menu menu){
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.side, menu);
return true;
}
#Override
public boolean onOptionsItemSelected (MenuItem item){
switch (item.getItemId()) {
case R.id.item1:
FirebaseAuth.getInstance().signOut();
finish();
startActivity(new Intent(new Intent(this, MainActivity.class)));
Toast.makeText(this, "Logged out ", Toast.LENGTH_SHORT).show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
#Override
protected void onStart() {
super.onStart();
DatabaseNote.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
privateNoteList.clear();
for (DataSnapshot privateSnapshot: dataSnapshot.getChildren() ){
PrivateNote privateNote = privateSnapshot.getValue(PrivateNote.class);
privateNoteList.add(privateNote);
}
PrivateList adapter = new PrivateList(HomeActivity.this,privateNoteList);
listViewNotes.setAdapter(adapter);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
private void showUpdateDialog(final String noteId, String noteDescription){
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
LayoutInflater inflater = getLayoutInflater();
final View dialogview = inflater.inflate(R.layout.update_dialouge,null);
dialogBuilder.setView(dialogview);
final EditText editDescription = dialogview.findViewById(R.id.editDescription);
final Button buttonUpdate = dialogview.findViewById(R.id.buttonUpdate);
final Button buttonDelete = dialogview.findViewById(R.id.buttonDelete);
dialogBuilder.setTitle("Updating Note: " +noteDescription);
final AlertDialog alertDialog = dialogBuilder.create();
alertDialog.show();
buttonUpdate.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String description = editDescription.getText().toString().trim();
if (TextUtils.isEmpty(description)){
editDescription.setError(" New information required");
return;
}
updatePrivateNote(noteId,description);
alertDialog.dismiss();
}
});
buttonDelete.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
deletePrivateNote(noteId);
}
});
}
private void deletePrivateNote(String noteId) {
DatabaseReference drPrivateNote = FirebaseDatabase.getInstance().getReference("privatenote").child(noteId);
drPrivateNote.removeValue();
Toast.makeText(this, " Note Deleted", Toast.LENGTH_SHORT).show();
}
private boolean updatePrivateNote(String id,String description){
DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference("privatenote").child(id);
PrivateNote privateNote = new PrivateNote(id,description);
databaseReference.setValue(privateNote);
Toast.makeText(this, " Note Updated", Toast.LENGTH_SHORT).show();
return true;
}
private void addnote () {
String description = Descriptionholder.getText().toString().trim();
if (!TextUtils.isEmpty(description)){
//generated unique number for id
String id = DatabaseNote.push().getKey();
PrivateNote pNote = new PrivateNote(id, description);
DatabaseNote.child(id).setValue(pNote); //pNote value added in id
Toast.makeText(this, "Note added", Toast.LENGTH_SHORT).show();
}
else {
Toast.makeText(this, " Please Enter a note ", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onBackPressed() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Are you sure you want to exit?")
.setCancelable(false)
.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
FirebaseAuth.getInstance().signOut();
finish();
}
})
.setNegativeButton("No", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
}
Adapter:
public class PrivateList extends ArrayAdapter<PrivateNote> {
private Activity context;
private List<PrivateNote> privateNoteList;
public PrivateList(Activity context, List<PrivateNote> privateNoteList){
super(context, R.layout.list_layout,privateNoteList);
this.context =context;
this.privateNoteList=privateNoteList;
}
#NonNull
#Override
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
LayoutInflater inflater = context.getLayoutInflater();
View listViewItem = inflater.inflate(R.layout.list_layout,null,true);
TextView textViewDescription = listViewItem.findViewById(R.id.TextViewDescription);
PrivateNote privateNote = privateNoteList.get(position);
textViewDescription.setText(privateNote.getNoteDescription());
return listViewItem;
}
}
PrivateNote:
public class PrivateNote {
public String noteId;
public String noteDescription;
public PrivateNote(){
}
PrivateNote(String noteId, String noteDescription){
this.noteId = noteId;
this.noteDescription=noteDescription;
}
String getNoteId()
{
return noteId;
}
String getNoteDescription()
{
return noteDescription;
}
}
In Firebase, there are two things,
FirebaseDatabase
FirebaseAuth
Usually, in almost all the apps, user data is stored in the FirebaseDatabase under the key, that is generated while creating a new user in your Firebase app.
So, for example, your database structure will look like this.
-Your_main_database
|____UserId_Of_FirebaseUser
|____stuff_related_to_user
|____More stuff related to user
So, you create a new user in FirebaseAuth, you can find more about it below:
Firebase Custom Auth.
Then, after creating a new user, you create child nodes under your database with the key=userId of your current logged in user.
eg. In your addNote function, the id variable will be equal to
String id = FirebaseAuth.getInstance().getCurrentUser().getUid();
Currently, you're using Push keys which are generated based on Timestamp, which you won't be able to associate with user unless you add a key inside your note object which stored current username, and then find all the child nodes that contain that username, but in that case, your data won't be organized at all.
Then, after you create a node with userId under your main database, you can then push new notes created by your user inside the userId with the push() function. So your database structure will look like below.
___
|
|__users
|__uniqueUserId
| |__note1
| |__note2
|__uniqueUserId2
|__note1
|__note2
|__note3
Next time whenever you want to fetch user created notes, you log in the user, get his ID, and then find the notes corresponding to that ID.
I don't see how you can fit SharedPreferences in there, before there is also function to cache data offline in Firebase once it is loaded.
Securing the notes:
If you implement the database like I said above, you'll be very easily able to secure your database.
Common Database Rules.
If you implement everything correctly like I told, you'll be able to secure your database with the fourth set of rules from above rules.
Since you are using firebase authentication, then you can retrieve the userId, and create the following database:
notes
userId
note : "todo"
description : "study"
Then you can do:
DatabaseReference ref = FirebaseDatabase.getInstance().getReference("notes");
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
String userId = user.getUid();
ref.orderByKey().equalTo(userId).addValueEventListener(new ValueEventListener() {...}
This way you would retrieve the data of the currently logged in user only.
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.
I have a List View in one activity with one info icon in custom adapter. When user taps on that info button then the next activity will open and after marking attendance in next activity when user taps the update button then the second activity should finish and first activity listview should be updated.
What i successfully did:
I have successfully mark the attendance and change the color of listview but i did that after closing the second activity and restarting the first activity. In this way the listview gets updated because of starting activity again.
What I am unable to do:
I want that when user taps on update button then only finish() will call and user returns to previous first activity with listview updated. But when i do so then the listview not get updated. I have to go back and open the activity again then the listview gets updated otherwise not. I do not want that. I also tried to notify adapter in the onResume method of first activity so that when user returns from second activity then the first activity listview adapter will be updated because of onResume method but it isn't work. Please Help
My Listview Activity Code:
public class TeacherWebserviceMainList extends Activity {
int attentedncemarkedCount = 0;
TextView addteacher;
DatabaseHandler databasehandler;
DetailsTeacherwebservice details;
String emis;
ArrayList<DetailsTeacherwebservice> addas = new ArrayList<DetailsTeacherwebservice>();
CustomAdapterTeacherWebservice cusadapter;
ArrayList<DetailsTeacherwebservice> teacherList;
private ListView listcontent = null;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.teacherwebservicemainlist );
addteacher = (TextView) findViewById(R.id.addteachermenu);
databasehandler = new DatabaseHandler(TeacherWebserviceMainList.this);
listcontent = (ListView) findViewById(R.id.teacher_list);
teacherList = databasehandler.teacherwebserviceList(emis);
Rsults();
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1) {
if(resultCode == RESULT_OK) {
String update = data.getStringExtra("update");
if(update.equals("1"))
{
//cusadapter.
CustomAdapterTeacherWebservice adapter = new CustomAdapterTeacherWebservice(this, addas);
listcontent.setAdapter(adapter);
}
}
}
}
private void Rsults() {
addas.clear();
//DatabaseHandler databaseHandler=new DatabaseHandler(this);
//ArrayList<ArrayList<Object>> data = databaseHandler.abcTeacherNew();
for (int p = 0; p < teacherList.size(); p++) {
details = new DetailsTeacherwebservice();
//ArrayList<Object> baris = data.get(p);
details.setId(teacherList.get(p).getId());
details.setTeachername(teacherList.get(p).getTeachername());
details.setTeachercnic(teacherList.get(p).getTeachercnic());
details.setTeacherno(teacherList.get(p).getTeacherno());
details.setTeachergender(teacherList.get(p).getTeachergender());
details.setAttendance(teacherList.get(p).getAttendance());
details.setTeacherattendancedetails(teacherList.get(p).getTeacherattendancedetails());
details.setAttendancedatesince(teacherList.get(p).getAttendancedatesince());
details.setAttendancetrasnferschool(teacherList.get(p).getAttendancetrasnferschool());
addas.add(details);
}
cusadapter = new CustomAdapterTeacherWebservice(TeacherWebserviceMainList.this, addas);
listcontent.setAdapter(cusadapter);
listcontent.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
}
});
}
My List Adapter Code
public class CustomAdapterTeacherWebservice extends BaseAdapter {
private static ArrayList<DetailsTeacherwebservice> searchArrayList;
DatabaseHandler databaseHandler;
private Context context;
private LayoutInflater mInflater;
public CustomAdapterTeacherWebservice(Context context, ArrayList<DetailsTeacherwebservice> results) {
searchArrayList = results;
mInflater = LayoutInflater.from(context);
databaseHandler = new DatabaseHandler(context);
}
#Override
public int getCount() {
return searchArrayList.size();
}
#Override
public Object getItem(int p) {
return searchArrayList.get(p);
}
#Override
public long getItemId(int p) {
return p;
}
public int getViewTypeCount() {
return 500;
}
#Override
public int getItemViewType(int position) {
return position;
}
#Override
public View getView(final int p, View v, ViewGroup parent) {
ViewHolder holder;
context = parent.getContext();
if (v == null) {
v = mInflater
.inflate(R.layout.teacherwebserviceadapter, null);
holder = new ViewHolder();
holder.name = (TextView) v.findViewById(R.id.teacher_name);
holder.cnic = (TextView) v.findViewById(R.id.teacher_cnic);
holder.no = (TextView) v.findViewById(R.id.teacher_phone);
holder.gender = (TextView) v.findViewById(R.id.gender);
holder.status = (TextView) v.findViewById(R.id.status);
holder.info = (ImageView) v.findViewById(R.id.edit);
holder.l1 = (LinearLayout) v.findViewById(R.id.main);
v.setTag(holder);
} else {
holder = (ViewHolder) v.getTag();
}
holder.name.setText(searchArrayList.get(p).getTeachername());
holder.cnic.setText(searchArrayList.get(p).getTeachercnic());
holder.no.setText(searchArrayList.get(p).getTeacherno());
holder.gender.setText(searchArrayList.get(p).getTeachergender());
holder.status.setText(searchArrayList.get(p).getAttendance());
if (searchArrayList.get(p).getAttendance().equals("Absent"))
{
holder.l1.setBackgroundColor(Color.parseColor("#DB674D"));
}
if (searchArrayList.get(p).getAttendance().equals("Present"))
{
holder.l1.setBackgroundColor(Color.parseColor("#7EB674"));
}
if (searchArrayList.get(p).getAttendance().equals("Transfer Out"))
{
holder.l1.setBackgroundColor(Color.parseColor("#FBE87C"));
}
if (searchArrayList.get(p).getAttendance().equals("Resigned"))
{
holder.l1.setBackgroundColor(Color.parseColor("#4FC3F7"));
}
holder.info.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent view_order_intent = new Intent(context, Teacherwebservicemainlistupdate.class);
view_order_intent.putExtra("ID", searchArrayList.get(p).getId());
view_order_intent.putExtra("tname", searchArrayList.get(p).getTeachername());
view_order_intent.putExtra("tgender", searchArrayList.get(p).getTeachergender());
view_order_intent.putExtra("tcnic", searchArrayList.get(p).getTeachercnic());
view_order_intent.putExtra("tno", searchArrayList.get(p).getTeacherno());
view_order_intent.putExtra("tatt", searchArrayList.get(p).getAttendance());
view_order_intent.putExtra("tattdetails", searchArrayList.get(p).getTeacherattendancedetails());
view_order_intent.putExtra("tattdatesince", searchArrayList.get(p).getAttendancedatesince());
view_order_intent.putExtra("tatttrasnferout", searchArrayList.get(p).getAttendancetrasnferschool());
//context.startActivity(view_order_intent);
((Activity)context).startActivityForResult(view_order_intent, 1);
}
});
return v;
}
static class ViewHolder {
TextView name, cnic, no, gender,status;
ImageView info;
LinearLayout l1;
}
here in adapter code when INFO button is clicked then another activity starts in which user can update the attendance.
This is Update activity code when button clicked:
update.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
DetailsTeacherwebservice schoolinfo = new DetailsTeacherwebservice();
schoolinfo.setTeachername(teachername.getText().toString());
schoolinfo.setTeacherno(teacherno.getText().toString());
schoolinfo.setTeachercnic(teachercnic.getText().toString());
schoolinfo.setTeachergender(teachergender.getText().toString());
schoolinfo.setAttendance(teachergroupstr);
schoolinfo.setTeacherattendancedetails(absentgrpstr);
schoolinfo.setAttendancedatesince(txtDate.getText().toString());
schoolinfo.setAttendancetrasnferschool(transferOutSchool.getText().toString());
databasehandler.updateteacherwebservice(schoolinfo, emis, identity);
Toast.makeText(Teacherwebservicemainlistupdate.this, "Updated Successfully", Toast.LENGTH_SHORT).show();
Intent intent = new Intent();
intent.putExtra("update", "1");
setResult(RESULT_OK, intent);
finish();
}
}
});
I can start the listview activity again when update button is clicked but that changes the index of the list item clicked i.e. because activity starts again. How ever what i want is that if i clicked on 10th item then when next activity opens and user updates attendance then it returns back to previous activity on same index position so that the user do not have to scroll again to go back on 10th item
Add this code for list item click:
Intent i = new Intent(context, YourAcitivityName.class);
i.putExtra("position",p);
startActivityForResult(i, 1);
Add this code in update button:
Intent intent = new Intent();
intent.putExtra("update", "1");
intent.putExtra("position",getIntent().getIntExtra("position",0));
setResult(RESULT_OK, intent);
finish();
When you get back from activity to the activity where list view is implemented this method called:
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1) {
if(resultCode == RESULT_OK) {
String update = data.getStringExtra("update");
if(update.equals("1"))
{
// add your code to update list view
teacherList = databasehandler.teacherwebserviceList(emis);
Rsults();
YourAdapterClassName adapter = new YourAdapterClassName(this, arrList);
listView.setAdapter(adapter);
listView.smoothScrollToPosition(getIntent().getIntExtra("position",0));
}
}
}
}
The following are two examples, the first is an example where the source for the ListView is a List (and in this case using a Custom Adapter), the second is where the source is a Cursor (using SimpleCursorAdapter)
The trap many fall into is just updating the List using something like :-
myList = method();
myAdapter.notifyDataSetChanged();
At a guess, that's the trap you've fallen into.
When what should be done is to use the List's methods to alter it (e.g. clear, add, addAll, remove).
I believe this explains the reasoning - Is Java “pass-by-reference” or “pass-by-value”?
Example 1 - List as the source :-
/**
* Build or rebuild the List of TaskData's, instantiate the
* Custom Array Adapter if needed and also notify the adapter
* if the List may have been changed.
*/
private void prepTasksCustomArrayList() {
// Determine if the adpater needs to be notified of changes
boolean notify_dataset_changed = (mCsr != null && mCustomArrayAdapter != null);
// If the List is not null then assume that it has changed
// otherwise just get the List
if (mTasks != null) {
mTasks.clear();
mTasks.addAll(dbhlpr.getAllTasks());
} else {
mTasks = dbhlpr.getAllTasks();
}
// If the adapter is null then instantiate it
if (mCustomArrayAdapter == null) {
mCustomArrayAdapter = new CustomArrayAdapter(
this,
R.layout.taskdata_item,
mTasks);
}
// Notify the adapter that the List has changed
if (notify_dataset_changed) {
mCustomArrayAdapter.notifyDataSetChanged();
}
}
Notes
This is used to both setup and alter/update the ListView that the adpater is attached to.
All that is needed is to call the method at the appropriate place/s in the code e.g. in the Activities onCreate, onResume, after onItemClick/LongClick.
e.g in onCreate :-
mTaskList = this.findViewById(R.id.tasklist); // The ListView
prepTasksCustomArrayList()
mTaskList.setAdapter(mCustomArrayAdapter)
and in onresume just (in conjunction with the 3 lines in onCreate)
prepTasksCustomArrayList()
I don't believe that this way is very common, normally you see the adapter being setup in onCreate.
mTasks.addAll(dbhlpr.getAllTasks()); is what gets the source data.
Example 2 - Cursor as the Source
/**
* Build or rebuild the List via Cursor using the bespoke layout
*/
private void prepCursorListBespoke() {
boolean swapcursor = (mCsr != null && mCursorAdapter2 != null);
if (mCursorAdapter2 == null) {
mCursorAdapter2 = new SimpleCursorAdapter(this,R.layout.taskdata_item,mCsr,
new String[]{ Datasource.TASKS_ID_COL,
Datasource.TASKS_TYPE_COL,
Datasource.TASKS_NAME_COL,
Datasource.TASKS_OWNER_COL,
Datasource.TASKS_EXPIRATION_COL},
new int[]{R.id.task_id,
R.id.task_type,
R.id.task_name,
R.id.task_owner,
R.id.task_expiration},
0);
}
if (swapcursor) {
mCursorAdapter2.swapCursor(mCsr);
}
}
Notes
The prepCursorListBespoke method is used in the same way as for example 1.
notifyDataSetChanged could be used instead of swapCursor
I use swapCursor because it's more descriptive).
However, you can only use swapCursor for Cursors.
Added
The following changes may work, roughly speaking I've applied the List example above.
i.e. Rsults is called by onResume(added) which will notify the adapter that the data has been changed.
The code hasn't been tested as there was insufficient code. (a no code for DatabaseHandler class, and b) no code for DetailsTeacherwebservice class). As such there may be errors.
Look for //++++++++++++++... comments should follow to say what has been done/changed.
public class MainActivity extends Activity {
int attentedncemarkedCount = 0;
TextView addteacher;
DatabaseHandler databasehandler;
DetailsTeacherwebservice details;
String emis;
ArrayList<DetailsTeacherwebservice> addas = new ArrayList<DetailsTeacherwebservice>();
CustomAdapterTeacherWebservice cusadapter;
ArrayList<DetailsTeacherwebservice> teacherList;
private ListView listcontent = null;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.teacherwebservicemainlist);
addteacher = (TextView) findViewById(R.id.addteachermenu);
databasehandler = new DatabaseHandler(TeacherWebserviceMainList.this);
listcontent = (ListView) findViewById(R.id.teacher_list);
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//MOVED On ItemClickListener block FROM Rsults to here.
listcontent.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
}
});
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
teacherList = databasehandler.teacherwebserviceList(emis);
Rsults();
}
// Probably don't even need onActivityResult
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1) {
if (resultCode == RESULT_OK) {
String update = data.getStringExtra("update");
if (update.equals("1")) {
//cusadapter.
//CustomAdapterTeacherWebservice adapter = new CustomAdapterTeacherWebservice(this, addas);
//+++++++++++++++++++++++++++++++++++
// Commented out
//listcontent.setAdapter(adapter);
}
}
}
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Added onResume to call Rsults
#Override
public void onResume() {
super.onResume();
Rsults();
}
private void Rsults() {
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// ADDED 4 lines to see if the notifyDataSetChanged is required
boolean notifydschg_needed = false;
if (cusadapter != null) {
notifydschg_needed = true;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// YOU NEED TO GET THE TEACHER LIST AGAIN AS THE DB HAS CHANGED
teacherList = databasehandler.teacherwebserviceList(emis);
addas.clear();
//DatabaseHandler databaseHandler=new DatabaseHandler(this);
//ArrayList<ArrayList<Object>> data = databaseHandler.abcTeacherNew();
for (int p = 0; p < teacherList.size(); p++) {
details = new DetailsTeacherwebservice();
//ArrayList<Object> baris = data.get(p);
details.setId(teacherList.get(p).getId());
details.setTeachername(teacherList.get(p).getTeachername());
details.setTeachercnic(teacherList.get(p).getTeachercnic());
details.setTeacherno(teacherList.get(p).getTeacherno());
details.setTeachergender(teacherList.get(p).getTeachergender());
details.setAttendance(teacherList.get(p).getAttendance());
details.setTeacherattendancedetails(teacherList.get(p).getTeacherattendancedetails());
details.setAttendancedatesince(teacherList.get(p).getAttendancedatesince());
details.setAttendancetrasnferschool(teacherList.get(p).getAttendancetrasnferschool());
addas.add(details);
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//Just create the adapter and attach it to the listview once
if (cusadapter == null) {
cusadapter = new CustomAdapterTeacherWebservice(TeacherWebserviceMainList.this, addas);
listcontent.setAdapter(cusadapter);
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Notify the adapter that the data has been changed
if(notifydschg_needed) {
cusadapter.notifyDataSetChanged();
}
}
}
// you have to set data in adapter in onResume method
#Override
protected void onResume() {
super.onResume();
}
You can use eventbus for this case:
This is an example:
In your build.gradle file
compile 'org.greenrobot:eventbus:3.0.0'
In Activity has listview:
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this); // register event
}
#Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this); // unregister event
}
#Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(EventBusInfo eventBusInfo) {
if (eventBusInfo.getProcessId() == 99) {
// eventBusInfo.getData();
notifyDataSetChanged();
}
}
In Update Activity
yourButtonFinish.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
finish();
EventBusInfo event = new EventBusInfo();
event.setProcessId(99);
//event.setData(object) //if you want get data back
EventBus.getDefault().post(event);
}
});
Class EventBusInfo
public class EventBusInfo {
private int processId;
private Object data;
public EventBusInfo(int processId, Object data) {
this.processId = processId;
this.data = data;
}
public EventBusInfo() {
}
public EventBusInfo(int processId) {
this.processId = processId;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public int getProcessId() {
return processId;
}
public void setProcessId(int processId) {
this.processId = processId;
}
}
save the list items in every change then restart the Activity of adapter with intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) to close the old ListView.
Is what I do and it works fine.
if the list it is not empty, I restart the activity with the adapter and I have a new ListView closing the previous one. Or if it is empty, I start a different activity (to not have an empty ListView)
Saving and loading the list items every time to SharedPreferences with Gson
For a reason notifyDataSetChanged() does not work good in my case so I prefer this solution.
I'm working on a simple log app that lets the user enter a time and a note, and then displays the entered data in a ListView in a dedicated activity (MainActivty). The time and data are entered in a separate activity (AddTimeActivity) with two EditText's and are passed to MainActivity when tapping the save button through an adapter (TimeTrackerAdapter). Alternatively, a cancel button can be pressed when the user changes their mind. The AddTimeActivity can be accessed through an add button in the action bar default menu. Now I've added a delete button -which is working fine- and an edit button to each row in the list. Now The problem is: How can I add the editing feature without making a new activity dedicated to editing. In Other words, how can I make the AddTimeActivity work with editing and adding in the same time, how can I make my app know that the user tapped the add button and start the AddTimeActivity with empty EditText's, or the user tapped the edit button in one of the rows in the list and passes the the data to be edited to AddTimeActivity and displays them in the EditText's and saves the edited data in the same entry? Sorry for not showing any attempts but I'm actually clueless about the issue.
MainActivity.java
public class MainActivity extends AppCompatActivity {
public TimeTrackerAdapter timeTrackerAdapter;
public int TIME_ENTRY_REQUEST_CODE = 1;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = (ListView) findViewById(R.id.time_list);
timeTrackerAdapter = new TimeTrackerAdapter();
listView.setAdapter(timeTrackerAdapter);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
super.onCreateOptionsMenu(menu);
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.menu_main, menu);
return true;
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == TIME_ENTRY_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
Bundle bundle = data.getExtras();
String time = bundle.getString("time");
String note = bundle.getString("note");
TimeRecord timeRecord = new TimeRecord(time, note);
timeTrackerAdapter.addTimeRecord(timeRecord);
timeTrackerAdapter.notifyDataSetChanged();
}
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
// noinspection SimplifiableIfStatement
if (id == R.id.add_time_item) {
Intent intent = new Intent(getApplicationContext(), AddTimeActivity.class);
startActivityForResult(intent, TIME_ENTRY_REQUEST_CODE);
return true;
}
else if (id == R.id.about) {
Intent aboutIntent = new Intent(getApplicationContext(), AboutScreen.class);
startActivity(aboutIntent);
}
return super.onOptionsItemSelected(item);
}
}
The AddTimeActivity, onSave and onCancel are the buttons' methods:
public class AddTimeActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.add_time);
}
public void onCancel(View view) {
finish();
}
public void onSave(View view) {
Intent intent = new Intent(AddTimeActivity.this, MainActivity.class);
EditText timeEditText = (EditText) findViewById(R.id.Time_Edit_Text);
String time = timeEditText.getText().toString();
EditText noteEditText = (EditText) findViewById(R.id.Note_Edit_Text);
String note = noteEditText.getText().toString();
intent.putExtra("time", time);
intent.putExtra("note", note);
this.setResult(RESULT_OK, intent);
finish();
}
}
TimeTrackerAdapter.java:
public class TimeTrackerAdapter extends BaseAdapter {
public ArrayList<TimeRecord> times = new ArrayList<TimeRecord>();
#Override
public int getCount() {
return times.size();
}
public TimeTrackerAdapter() {
times.add(new TimeRecord("12:30", "this is the best"));
times.add(new TimeRecord("2:30", "I need this"));
}
#Override
public Object getItem(int position) {
return times.get(position);
}
public void addTimeRecord(TimeRecord timeRecord) {
times.add(timeRecord);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(final int position, View view, ViewGroup parent) {
if (view == null) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
view = inflater.inflate(R.layout.menu_layout, parent, false);
}
TextView timeView = (TextView) view.findViewById(R.id.time_textView);
TextView noteView = (TextView) view.findViewById(R.id.note_TextView);
Button deleteButton = (Button) view.findViewById(R.id.delete_entry);
deleteButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
times.remove(position);
notifyDataSetChanged();
}
});
TimeRecord time = times.get(position);
timeView.setText(time.getTime());
noteView.setText(time.getNote());
return view;
}
}
The question is
How can I add the editing feature without making a new activity dedicated to editing.
or, more generally,
"how do I hand over information to the activity I'm calling ?"
You achieve this by adding extras to the intent which you use to start the activity. For example, in your 'MainActivity' before calling 'startActivityForResult()':
Intent intent = new Intent(this, MyOtherActivity.class);
// in your case, 'extraInformation' could be a boolean (add = yes|no)
intent.putExtra("MyExtraInformationKey", extraInformation);
startActivityForResult(intent,TIME_ENTRY_REQUEST_CODE);
Then in the 'onCreate()' method of the other activity, you question the intent for extras:
Intent i = getIntent();
if (i != null && i.hasExtra(getString("MyExtraInformationKey"))
{
boolean myInfo = i.getBooleanExtra("MyExtraInformationKey");
// proceed as appropriate...
}
For your case of buttons inside ListView rows, you could make the OnClickListener method call another method (like 'doCallMayOtherActivity()') in your 'MainActivity', handing over all relevant information (like the position in the 'times' ArrayList).
This method would then start your add/edit activity, passing the old data out of 'times.get(position)' in a bundle as an extra to the intent.
To access the methods in 'MainActivity' from your adapter class, you could use the following code in 'getView()'
Button editButton=(Button) view.findViewById(R.id.edit_entry);
editButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
((MainActivity)parent.getContext()).doCallMyOtherActivity(times.get(position));
}
This way, your other activity could check for the existence of that bundle. If there is none, you have a case of 'add'. Else, you have a case of 'edit'.
I have a ListView, which displays a list of notes taken from a table in SQLiteDatabase with ArrayAdapter.
public class NotepadActivity extends ListActivity {
protected static final int ADD_NEW_NOTE = 0;
protected static final int EDIT_NOTE = 1;
ArrayAdapter<Note> adapter;
NotesManager manager;
private Note nNoteToDelete;
ArrayList<Note> lstAllNotes;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
findViewById(R.id.btnAddNewNote).setOnClickListener(addNewNote);
manager = new NotesManager(this);
lstAllNotes = manager.getAllNotes();
adapter = new ArrayAdapter<Note>(this, R.layout.note, lstAllNotes);
setListAdapter(adapter);
getListView().setOnItemClickListener(editNote);
registerForContextMenu(getListView());
}
When I click on an Item in this ListView, it takes this Note object to the EditNote activity:
private OnItemClickListener editNote = new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
Note currNote = (Note)parent.getItemAtPosition(position);
int curr_note_id = currNote.getId();
String curr_note_title = currNote.getTitle().toString();
String curr_note_details = currNote.getDetails().toString();
Intent editNote = new Intent(NotepadActivity.this, EditNote.class);
editNote.putExtra("CURR_NOTE_ID", curr_note_id);
editNote.putExtra("CURR_NOTE_TITLE", curr_note_title);
editNote.putExtra("CURR_NOTE_DETAILS", curr_note_details);
startActivityForResult(editNote, EDIT_NOTE);
}
};
I can edit the title of the note in there and the content. When I hit the Save button, it sends back to the main activity the Strings of Title and the Details and the ID int:
public class EditNote extends Activity implements OnClickListener {
int curr_note_id;
String curr_note_title;
String curr_note_details;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.editnote);
curr_note_id = getIntent().getExtras().getInt("CURR_NOTE_ID");
curr_note_title = getIntent().getExtras().getString("CURR_NOTE_TITLE");
curr_note_details = getIntent().getExtras().getString(
"CURR_NOTE_DETAILS");
((EditText) findViewById(R.id.edtTitle)).setText(curr_note_title);
((EditText) findViewById(R.id.edtDetails)).setText(curr_note_details);
findViewById(R.id.btnSave).setOnClickListener(this);
findViewById(R.id.btnCancel).setOnClickListener(this);
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnSave:
String strNoteTitle = ((EditText) findViewById(R.id.edtTitle)).getText().toString().trim();
String strNoteDetails = ((EditText) findViewById(R.id.edtDetails)).getText().toString().trim();
if (!strNoteTitle.equals("")) {
Intent data = new Intent();
data.putExtra("NOTE_ID", curr_note_id);
data.putExtra("NOTE_TITLE", strNoteTitle);
data.putExtra("NOTE_DETAILS", strNoteDetails);
setResult(RESULT_OK, data);
finish();
} else {
((EditText) findViewById(R.id.edtTitle))
.setError("A note must contain a title");
}
break;
case R.id.btnCancel:
setResult(RESULT_CANCELED);
finish();
break;
}
}
}
And in the main activity it should update the DB and the ListView with the new Title, if there were changes. My code does update the DB, but I don't see the change in the ListView immediately, only if I exit the app and open it again. Here's the code (watch the second case, EDIT_NOTE):
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
switch (requestCode) {
case ADD_NEW_NOTE:
String noteTitle = data.getExtras().getString("NOTE_TITLE");
String noteDetails = data.getExtras().getString("NOTE_DETAILS");
Note nNewNote = new Note(-1, noteTitle, noteDetails);
adapter.add(nNewNote);
manager.addNote(nNewNote);
break;
case EDIT_NOTE:
int noteID = data.getExtras().getInt("NOTE_ID");
noteTitle = data.getExtras().getString("NOTE_TITLE");
noteDetails = data.getExtras().getString("NOTE_DETAILS");
nNewNote = new Note(noteID, noteTitle, noteDetails);
Toast.makeText(NotepadActivity.this, noteTitle + "\n" + noteDetails, Toast.LENGTH_SHORT).show();
manager.updateNote(nNewNote);
break;
}
}
super.onActivityResult(requestCode, resultCode, data);
}
After manager.updateNote(nNewNote); I tried to use adapter.notifyDataSetChanged(); but that didn't work - I didn't see the change in the ListView. Perhaps I used it wrong, perhaps I should use something else...
So how can I make the ListView refresh? How can I see change immediately, without restarting the app?
Thank you!
When you return from the edit activity, set the list adapter again to refresh the ListView. You have to manually refresh the ListView each time if you want to see any updates. Hope this helps.