Currently I can check/uncheck the checkboxes by groupPosition and childPosition, and it's working fine. I save the groupPosition and childPosition to the SQLite Database.
When I added the delete option, my checkbox checked states are off/messed up.
For example, I have the following:
Group1,
Child1, Child2, Child3
I check all 3 of them and save the checked positions to the SQLite Database.
The checked positions would be 0,0 and 0,1 and 0,2 in the Database.
After I deleted Child1,
Child2 becomes position 0, Child3 becomes position 1 which would mess up the checked states.
I am trying to find a way to save the group name and child name instead of the positions to the SQLite Database, then load those names in Oncrete method.
MainActivity:
if(category_array.get(groupPosition).subcategory_array.get(childPosition).selected) {
category_array.get(groupPosition).subcategory_array.get(childPosition).selected = false;
try{
MySQLITE_DATABASE.deleteRows(groupPosition, childPosition);
}
catch (Exception e) {}
}
else
{
category_array.get(groupPosition).subcategory_array.get(childPosition).selected = true;
MySQLITE_DATABASE.addRow(groupPosition, childPosition);
}
Adapter code:
private List<Category> mGroupCollection;
if(mGroupCollection.get(groupPosition).subcategory_array.get(childPosition).selected) {
childHolder.checkBox.setBackgroundResource(R.drawable.checkbox_checked);
} else {
childHolder.checkBox.setBackgroundResource(R.drawable.checkbox_unchecked);
}
Date Holder Classes:
public class Category {
public String category_name = null;
public String cat_SelectedChildCount_name;
public ArrayList<SubCategory> subcategory_array = new ArrayList<SubCategory>();
}
//==============================
public class SubCategory {
public String subcategory_name = null;
public boolean selected = false;
}
I want to save the group name and child name instead of positions to the SQLite Database and then load them in OnCreate method.
I have tried this but it is not working:
String Group_Name = category_array.get(Integer.parseInt(groupPosition)).category_name;
String Child_Name = category_array.get(Integer.parseInt(groupPosition)).
subcategory_array.get(Integer.parseInt(childPosition)).subcategory_name;
if(category_array.get(Group_Name).subcategory_array.get(Child_Name).selected) {
category_array.get(Group_Name).subcategory_array.get(Child_Name).selected = false;
MySQLITE_DATABASE.DeleteRow(Group_Name, Child_Name);
}
else {
category_array.get(Group_Name).subcategory_array.get(Child_Name).selected = true;
MySQLITE_DATABASE.AddRow(Group_Name, Child_Name);
}
I know that I need to change the code in the adapater, data holder classes, and mainactivity in order to make it work, but I am out of ideas. I have been thinking and thinking, but nothing works...
Can someone please please guide me on this?
I am sorry for the long code.
Thank you and thank you.
Try any of the following if it suit's your requirement.
If you want to use the position strictly (May be there is no unique option)
Keep the DB design like
| id | parent_position | child_position |
Assume you have N childs. If you delete a child (Suppose it's 0). do this
Update all the child's with (child_position = child_position - 1) whose position is greater than
the deleted child of the parent. So the position remains unchanged.
Personally i would suggest don't use the position if a delete option is there. Option that i found, But don't know your use case
If you are loading the list from the database use the database primarykey as the key to save it in the SQLite database on selection/unselection. In this case you don't want to know the parent position as well.
Related
Please feel free to skip to the question as this background understanding may not be necessary for you.
I am new to android and sqlite and I am designing an app which has a content provider to access an sqlite database with multiple tables. There are several activities which use different adapters to display info from the database to the UI (i.e. cursor adapters, fragment state page adapter and array adapters). I have been having issues with the delete function in all of my activities which don't use the cursor adapter. When I try to update or delete a row from a table it deletes the wrong row or it doesn't delete anything at all. I believe it is a problem with the adapter where I am trying to figure out which row it is to send the correct info to the content provider.
The identical java code works perfectly with the cursor adapter and the rows delete normally and the other CRUD operations work. The insert and query functions work normally for all tables.The provider code uses a switch statement for each table but it is basically identical for each Uri case. All of the tables have _id as the integer primary key which is NOT set to auto increment. Since I don't fully understand how the row id works my java code does not reflect it and I keep having these issues. Although I have read many documents about content providers, adapters, sqlite databases, etc. certain key details are not clear to me.
My question is how does the row id get assigned numbers in the database when it is set to _id column as a primary key and what happens to those numbers when the database is changed?
For example, say I have an empty database. Initially after inserting the first row, the Uri will return a path segment for the 0 row and the adapter position would be 0... what would the row id for the database be (0 or 1) ?
Then for each row I insert, I know that row number would increase by one integer. Say I insert 4 rows - 0,1,2,3. Now when I am ready to delete - should the last path segment on the Uri be one integer less than the row number (i.e do I send a Uri with a last path segment of 2 to delete row 3)? Finally, after deleting, will the row ids then automatically get re-assigned so that row 4 now becomes row 3 ? Is there some code that I need to write to make that happen in the database? The primary keys are not set to auto increment.
I have different adapters and activities to where I can not access the actual database row ID once the data is displayed in the UI, so I am using the adapter position as a surrogate. This is why I am having trouble with update and delete.
Thank you very much if you read this entire question and take the time to answer it, it would help me tremendously.
I have an activity that is tabbed and uses FragmentStatePagerAdapter that is populated by a database. Here is the Adapter that I adjusted to keep track of the rows:
**EDITED:**
public class TankSectionsPagerAdapter extends FragmentStatePagerAdapter {
private ArrayList<Fragment> tankFragments = new ArrayList<>();
private ArrayList<String> tankTitles = new ArrayList<>();
//I added this ArrayList below to store the tankIDs to match the Fragments//
**public ArrayList<Integer> tankIDs = new ArrayList<>();**
public TankSectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return tankFragments.get(position);
}
#Override
public int getCount() {
return tankFragments.size();
}
#Override
public String getPageTitle(int position) {
return tankTitles.get(position);
}
public void addPage(Fragment fragment, String tankName) {
tankFragments.add(fragment);
tankTitles.add(tankName);
// I added this below so the ID position would match each fragment position //
**tankIDs.add(tankId);**
notifyDataSetChanged();
}
// Finally I added this method below to the adapter//
** public ArrayList<Integer> getPageId(){
return tankIDs;
}**
Here is the activity where the method is called and where it pulls the data from the cursor to pass to the Adapter. There is a loop where each row creates a page(tab) in the ViewPager:
public class MyClass extends Tank implements TabLayout.OnTabSelectedListener, LoaderManager.LoaderCallbacks<Cursor> {
public TankSectionsPagerAdapter tankSectionsPagerAdapter;
TabLayout tabLayout;
private ViewPager mViewPager;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_class);
mViewPager = (ViewPager) findViewById(R.id.container);
addPages(mViewPager);
tabLayout = (TabLayout) findViewById(R.id.tabLayout);
tabLayout.setupWithViewPager(mViewPager);
tabLayout.addOnTabSelectedListener(this);
}
public void addPages(ViewPager mViewPager) {
tankSectionsPagerAdapter = new TankSectionsPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(tankSectionsPagerAdapter)
try {
...
Cursor cursor = getContentResolver().query(MyProvider.CONTENT_URI_TABLE_TANK_SETUP, MyDatabaseHelper.ALL_TABLE_TANK_SETUP_COLUMNS, tankDataFilter, null, null);
if (cursor.moveToFirst()) {
do {
tName = cursor.getString(cursor.getColumnIndex(MyDatabaseHelper.TANK_NAME)); ...
// all the variables are stored in the bundle passed to the fragment/
...
**tankSectionsPagerAdapter.addPage(MainTankFragment.newInstance(tankBundle),tName, int tankID);**
tankDataFilter = tankDataFilter + (-1);
}
while (cursor.moveToNext());
cursor.close();
} else {
Toast...
}
} catch (Exception e) {
Toast..
}
}
...
// Get Row ID from cursor(tankID), parameter in addPage() above//
//Get ID's from Adapter //
** ArrayList <Integer> pageID= tankSectionsPagerAdapter.getPageId();**
This is the Activity with Spinner to choose the rows/fragments to edit or delete.
public class EditTank extends Tank implements LoaderManager.LoaderCallbacks<Cursor>
...
// Get the ArrayList//
ArrayList<Integer> IDtags =getIDIntent.getIntegerArrayListExtra("tank_edit_key");
loadEditTankSpinnerData();
////***Here is the Spinner. Use row ID from the ArrayList******
Note: Don't use the id of the spinner
editTankSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view int position, long id) {
*** tankID = IDtags.get(position); ***
}
private void loadEditTankSpinnerData() {
List<String> tankNames = new ArrayList<String>();
Cursor cursor = getContentResolver().query(MyProvider.CONTENT_URI_TABLE_TANK_SETUP, MyDatabaseHelper.TANK_NAMES, null, null,null);
try{
if (cursor.moveToFirst()) {
do {
tankNames.add(cursor.getString(1));
} while (cursor.moveToNext());
cursor.close();
} else {
deleteTankBtn.setEnabled(false);
editTankBtn.setEnabled(false);
Toast...
}
} catch (Exception e) {
Toast...
}
...
}
The above code worked well with CursorAdapter but not with the fragmentStatePagerAdapter (***Prior to the edits it did not work, now it works well and deletes correctly).
I spent days(weeks) on this because I didn't understand why the rows weren't deleting. I hope this helps someone.
Word of advise - Try to write your question as simple as possible also the code. You shouldn't share too much code in here. People will just ignore.
You're using a CursorLoader but not using it properly. Use the LoadFinished method's cursor data.
Then you can directly pass the cursor to your FragmentPageAdapter and use it directly there.
Hope this helps.
Thanks to #pskink, #CL, and #albeee - this is what I learned from you guys and my own research.
In order to delete rows from database which is populating FragmentStatePagerAdapter or ArrayAdapter you have to be able to link the correct row with what is being displayed in the adapter. Otherwise the rows won't delete or will be inconsistent or incorrect. The CursorAdapter will automatically handle the watching for changes and selecting the ID for you. If you can use CursorAdapter or a direct onItemClickListener and get the id directly from the AdapterView with getSelectedId() or just long ID, then that is a good way to get the row ID. However, if you are getting the id indirectly by other means then you have to handle the associations...
1.You should not use the adapter position, spinner position or even spinner id to select the row. These are useful only to determine which item/fragment you want. Only the ID of the OnClickListener on the item itself will consistently give the correct row.
2.The database rows behave as such - the integer primary key will auto increment even if AUTOINCREMENT is not chosen, but the row ID's are stable once assigned. This means that each new row will have a higher ID than the last but if you delete rows in between, it will not change the IDs of the remaining rows. So the rows will skip and not be consecutive when there are edits and deletions. For this reason you must have a method to link the item to the ID permanently. There may be a better way to do this, however, I will edit the code above to show one way that I was able to do it for the FragmentStatePagerAdapter so the user can add and delete fragments dynamically.
I'm quite new with Espresso and I'm trying to test an insertion, deletion and modification in a ListView with custom adapter.
With insertion is simple, I just add a new element and find it inside ListView to check that it has been added.
But the problem comes with deletion, this is what I'm trying to do:
Click in first row, on deletion button.
Confirm delete.
¿How can I assure that the element has been removed?
I mean, I think that I need to know the content of the item before delete it and then check that the row does not exist any more.
How can I do it? Maybe I'm not in the right path to perform this test and I need to do the test in a different way, but I can not find any information.
#Test
public void deleteEdition() {
startActivityWithintent();
// push delete button and delete
onData(anything()).inAdapterView(withId(R.id.listEdition))
.atPosition(1)
.onChildView(withId(R.id.imageDelete))
.perform(click());
// confirm delete
onView(withId(R.id.buttonDelete)).check(matches(isDisplayed()));
onView(withId(R.id.buttonDelete)).perform(click());
// HOW CAN I CHECK THE ELEMENT DOES NOT EXISTS?
// onView(withId(R.id.listEdition))
// .check(matches(not(withText(????))));
}
Thanks in advance.
try to search if there is no view that has a certain text inside its childs, like this:
onView(withId(R.id.listEdition))
.check(matches(not(hasDescendant(withText("should not exist")))));
or if this does not work, search for parts of the string.
onView(withId(R.id.listEdition))
.check(matches(not(hasDescendant(withText(containsString("should not exist"))))));
it works when i try to reproduce it on my computer.
store the text of the row you want to delete in a property, like "matchedText"
this.matchedText = null;
this.rowCounter = -1;
onView(new Matcher<View>() {
#Override
public boolean matches(Object item) {
boolean matches = withId(R.id.imageDelete).matches(item);
if(matches){
rowCounter++;
if(rowCounter == 5){ // 5 is an example of the index you want to access
View view = (View) item;
matchedText = ((TextView)view.getParent()).getText().toString(); // find the matching view by navigating to the right parent and child to get the text you need
return true
}
return false;
}
return false;
}
#Override
public void describeMismatch(Object item, Description mismatchDescription) {
}
#Override
public void _dont_implement_Matcher___instead_extend_BaseMatcher_() {
}
#Override
public void describeTo(Description description) {
}
}).check(matches(isDisplayed()));
after clicking on the delete button you can find out if the row really was deleted with following code:
onView(withId(R.id.listEdition))
.check(matches(not(hasDescendant(withText(this.matchedText)))));
I implement RecyclerView which have two ViewType. The list is dynamic and when user scroll down/up it add some items. In my RecyclerView just one of the item has different ViewType (consider this as expandable list which only one of item expand at a time).
I save position for expanded item But when new data added this position changed and I lost expanded item. Because data added in scroll down/up, updating expanded item according to size of new data is not good Idea.
One thing to mention is that I want to scroll to expanded item at first load. so I guess saving position would be best choice. (I guess but I'm not sure);
I want to know what's the efficient way to handle this issue?
http://tutorials.jenkov.com/java-collections/hashcode-equals.html
Implement hashcode and equals method, using this get the position for the expanded model object.
Example :
public class Employee {
protected long employeeId;
protected String firstName;
protected String lastName;
public boolean equals(Object o){
if(o == null) return false;
if(!(o instanceof) Employee) return false;
Employee other = (Employee) o;
if(this.employeeId != other.employeeId) return false;
if(! this.firstName.equals(other.firstName)) return false;
if(! this.lastName.equals(other.lastName)) return false;
return true;
}
public int hashCode(){
return (int) employeeId;
}
}
// To get the index of expanded item
int expandedItemIndex = EmployeeList.indexOf(expandedEmployeeModel);
recyclerView.scrollToPosition(expandedItemIndex);
Likely you use model for loading data to RecyclerView. And this model (for ex. ContactModel) contains different values for your ViewHolders.
What is point, that you use special references for that saved position. What you need to do, it's just put that position (of expanded item) to current model. And after all most works fine.
I am creating a RecyclerView which has items. Each item has a CheckBox, ImageButton and a TextView as stated below:
public class RecyclerViewViewHolder extends RecyclerView.ViewHolder {
RelativeLayout item;
TextView id;
TextView name;
ImageButton delete;
CheckBox checkBox;
public RecyclerViewViewHolder(View view) {
super(view);
item = (RelativeLayout) view.findViewById(R.id.layout_item_item);
id = (TextView) view.findViewById(R.id.layout_item_id);
name = (TextView) view.findViewById(R.id.layout_item_name);
delete = (ImageButton) view.findViewById(R.id.layout_item_delete);
checkBox = (CheckBox) view.findViewById(R.id.layout_item_checkbox);
}
}
I want the checkbox view to be checked only if a certain value in a column of a database is equal to 1. If the value is 0, it should not be checked. I have started off with the code below:
SQLiteDatabaseAdapter database = new SQLiteDatabaseAdapter(context);
database.open();
Cursor cursor = database.getAllItems(table);
if (cursor.moveToFirst()) {
do {
if (cursor.getInt(2) == 0) {
viewHolder.checkBox.setChecked(false);
}
else {
viewHolder.checkBox.setChecked(true);
}
} while (cursor.moveToNext());
}
database.close();
This code works but it checks the first row in the database and if the checked column is 1, it changes every single CheckBox to be checked. I only want the CheckBox associated with one particular row to be checked, not every single one. How do I exactly do that?
The problem is that you are iterating through every row in the cursor. Instead, you need to go to the row for the current position by calling cursor.moveToPosition().
In pseudocode, the steps you should follow are
Open the database.
Get the cursor.
Move the cursor to the correct row.
Retrieve the value from the cursor.
Set the Checkbox to the correct state.
Close the cursor.
I leave the details as an exercise for the reader.
I recommend that you create a HashMap<> and link the IDs and the checked states. Then, you can reference from the HashMap<> and get the checked states.
HashMap<Integer, Integer> hashMap = new HashMap<Integer, Integer>();
Then, in your onBindViewHolder function, you can use the following to check you CheckBox:
if (hashMap.get(position) == 0) {
// not checked
}
else {
// checked
}
Make sure you add the IDs and checked states from your SQLite database.
I have a delete Row function as according:
public boolean removeData(int position) {
SQLiteDatabase db = this.getWritableDatabase();
db.delete(TABLE_NAME, COL_ID+"="+position, null);
return true;
}
This function deletes a row according to its unique ID.
How can I change this so that after deleting a row, all rows below that one will be moved up to fill the empty space in the database?
That's against the design principle of a relational database. The rows are not ordered in a predictable way. So after delete you can only be sure that the deleted record appears to be away, but you have no control on the physical locations of any record, including which record(s), if any, now cover the space of the deleted one.
Querying data is another topic. You can specify a sort order, available as a parameter with the query methods. When querying your table, the results will appear exactly as you want it: If previously your results were Adam, Eve, Jack, Michael, then after deleting Jack, the result will be Adam, Eve, Michael.
The interplay between the displayed list, the domain objects behind that list, and the database is a different topic. Here are a few code snippets I use for a similar task. The basic idea is, when reading the objects that will be displayed, to include the database id with the object. So, if I read a list of products, the the domain class Product will have an id field that gets set with the database id when reading it.
To get the domain object displayed at a specific list position (e.g. the one where a user hit a delete button), the code fragment is.
public void onClick(View view) {
Product product = (Product) ProductList.this.products.get(ProductAdapter.this.listView.getPositionForView((View) view.getParent()));
... now do whatever is necessary to delete the product, probably
calling a DAO class that deletes the object based on its id,
not the list position
ProductAdapter.this.notifyDataSetChanged();
}
Solved this by removing the row in the database by the text of the TextView in the ListView instead of removing by the position of the TextView.
Now looks like this:
//Erasebutton listener
final Button eraseButton = (Button) findViewById(R.id.eraseButton);
assert eraseButton != null;
eraseButton.setOnClickListener(new View.OnClickListener() { //erasebutton onclick
public void onClick(View eraseButton) {
SparseBooleanArray checked = questionList.getCheckedItemPositions();
for(int i = questionList.getCount() - 1; i >= 0; i--)
{
if(checked.get(i)) {
//What to do with selected listitems
TextView tv = (TextView) questionList.getChildAt(i).findViewById(R.id.checkedTextView1);
db.removeData(tv.getText().toString());
}
}
checked.clear();
Cursor newCursor = db.getData();
adapter.swapCursor(newCursor);
}
});
And removeData function now looks likte this:
public boolean removeData(String question) {
SQLiteDatabase db = this.getWritableDatabase();
db.delete(TABLE_NAME, COL_QUESTION+"='"+question+"'", null);
return true;
}