SQLite database insert is somehow vanishing? - android

I'm having a very strange issue on my Android app wherein when I am inserting a value to a DB table, the first entry is disappearing somehow. However, any subsequent entries are appearing fine.
To be a little more specific, part of my application allows users to create a simple log where they enter some text and when they save it, it shows up on a list of log entries. However, when I try to insert the very first entry to an empty table, that entry is not being displayed, nor does the database indicate there is any data when I query for a count.
Interestingly enough, when I look at the return of the database insert call (SQLiteDatabase.insert()) I see a valid row number returned. In fact, when I look at any log entry I've saved to the database, the row number is correctly incrementing. As per the docs, my understanding is that if a non-negative number is returned, the insert was successful.
Here is the code that takes the result of the EditText from my AlertDialog, creates a new log entry, and calls the insert method:
newPainLogEntryDialog.setPositiveButton("Save",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
//make new pain log entry
PainLog painLog = new PainLog();
painLog.setPainEntry(input.getText().toString());
painLog.setPainDateTime(Calendar.getInstance());
Database.init(PainLogTab.this.getActivity());
Database.createPainLog(painLog);
updatePainLogList();
//display success to user
Toast.makeText(PainLogTab.this.getActivity(),
"Log entry saved", Toast.LENGTH_SHORT).show();
}
});
The code for Database.createPainLog():
public static long createPainLog(PainLog painLog) {
ContentValues cv = new ContentValues();
cv.put(COLUMN_PAINLOG_ENTRY, painLog.getPainEntry());
cv.put(COLUMN_PAINLOG_DATETIME, painLog.getPainDateTimeString());
return getDatabase().insert(PAINLOG_TABLE, null, cv);
}
And the last call before the Toast message is updatePainLogList(), which gets all the DB entries:
public void updatePainLogList(){
Database.init(PainLogTab.this.getActivity());
final List<PainLog> painLogs = Database.getAllPainLogs();
painLogListAdapter.setPainLogs(painLogs);
Log.d(getClass().getSimpleName(), "number of painLogs found: " + painLogs.size());
getActivity().runOnUiThread(new Runnable() {
public void run() {
// reload content
PainLogTab.this.painLogListAdapter.notifyDataSetChanged();
if(painLogs.size() > 0){
getView().findViewById(android.R.id.empty).setVisibility(View.INVISIBLE);
}else{
getView().findViewById(android.R.id.empty).setVisibility(View.VISIBLE);
}
}
});
}
And for completion sake, the body of the getAll() and its accompanying method getCursor():
public static Cursor getPainLogCursor() {
String[] columns = new String[] {
COLUMN_PAINLOG_ID,
COLUMN_PAINLOG_ENTRY,
COLUMN_PAINLOG_DATETIME
};
return getDatabase().query(PAINLOG_TABLE, columns, null, null, null, null,
null);
}
public static List<PainLog> getAllPainLogs() {
List<PainLog> painLogs = new ArrayList<PainLog>();
Cursor cursor = Database.getPainLogCursor();
if (cursor.moveToFirst()) {
while (cursor.moveToNext()) {
PainLog painLog = new PainLog();
painLog.setId(cursor.getInt(IDX_PAINLOG_ID));
painLog.setPainEntry(cursor.getString(IDX_PAINLOG_ENTRY));
painLog.setPainDateTime(cursor.getString(IDX_PAINLOG_DATETIME));
painLogs.add(painLog);
}
}
cursor.close();
return painLogs;
}
Now with some code I can explain what debugging steps I have taken thus far. As mentioned above, when I look at the return of the DB insert, I get a positive, non-zero number. However, when I try to print the number of logs in the immediately following update method (no deletes or anything get called en route), it displays 0, and indeed if I follow the Cursor I find that it never enters the loop which adds logs to the list which is displayed, also indicating it is not picking up the entry.
I have tried to set the DB insert in a transaction so that I can manually commit, but this does not help either. What makes this more interesting to me is that I have similar functionality elsewhere in my app where I save user preferences and display them in a list, and this does not suffer from the same problem...I have compared against this code and couldn't find any differences that would cause it.
To sum it up, my question is two-fold: why is only my first insert on an empty table showing up as not there, while all following ones are fine?; why am I getting a valid return from the database insert and yet immediately following the insert when I query for that data it is missing?
Thanks in advance for any help you can provide :)

if (cursor.moveToFirst()) {
while (cursor.moveToNext()) {
This skips the first row in cursor. moveToFirst() moves to the first row and moveToNext() moves to the next one, skipping the first one.
You can replace this with just while (cursor.moveToNext()). When you get your cursor from a query, it is placed at index -1 first i.e. at the row before the first one.

if (cursor.moveToFirst()) {
while (cursor.moveToNext()) {
This would be the best solution for it....

Related

Should I use extra arraylist for efficient data operations while using Sqlite db with recyclerviews?

I hope straightforward questions.
1) I managed to get the data from Sqlite db and showing them on recyclerview. The question is for example when i click on the recyclerview items and do some operations (for example copying the content or updating) is it better to use an arraylist and get the data first when application loads then do the operations on this arraylist elements (then notifying db eventually)?
2) If there is no need for extra arraylist on onContextItemSelected() operations while clicking recyclerview item again, i ve some trouble in choosing the element and its values.
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.idshare :
//implicit intent
shareImplicitIntent();
return true;
......
for the shareImplicitIntent() method
private void shareImplicitIntent() {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
Cursor cursor=WordListOpenHelper.mReadableDB.rawQuery("SELECT * FROM
diary", null);
cursor.moveToPosition(XYZ);
Entry_Model entry_model= new Entry_Model();
entry_model.setmEntry(cursor.getString(cursor.getColumnIndex(WordListOpenHelper.KEY_ENTRY)));
String title = entry_model.getmEntry(); ......
basically using cursor and getting the title of the cursor at XYZ position.
But how can I choose that XYZ position ?
Working hours on it but couldnt find a clue. Please help me.Thanks a lot
To answer my question myself, shortly no, for example for getting input from the user and putting them in the arraylist then doing the database operations on the arraylist not very useful nor necessary. (Yet if your database is planned to hold only small amount of entries though you can use arraylist/linkedlists for fast CRUD manipulations on the recyclerview adapter).
For the second part of the question it s easy to copy the content of the clicked recyclerview element by creating setonclicklistener in the viewholder constructor of the viewholder innerclass, for example;
(note unlike in the example you dont have to use contentresolver if you dont plan to share the datas in the database with other applications)
itemView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
int pos =getAdapterPosition();
String entry = "";
String[] mProjection =
{ Contract.WordList.KEY_ENTRY, // Contract class constant for the _ID column name };
Cursor cursor = mContext.getContentResolver().query(Uri.parse(
queryUri), mProjection, null, null, sortOrder);
if (cursor != null) {
if (cursor.moveToPosition(pos)) {
int indexEntry = cursor.getColumnIndex(Contract.WordList.KEY_ENTRY);
entry = cursor.getString(indexEntry);
}
}
Toast.makeText(v.getContext(), "copied entry is " + entry, Toast.LENGTH_LONG).show();
return false;
}
});

Error while fetching data from a db

I have a table, c_question in which I stored some questions with this structure
autoincrement column _id,
question,
option1,
option2,
option3,
correct_answer
Now I want to retrieve the question in a TextView and the answers in a RadioGroup.
If the user selects the correct answer, then the question and options will change
in the same page.
Logcat: fatal exception at main ..... cursorIndexOutOfBoundException
The output shows the last data (question with answers) I entered in the db and if I click any answer, the app crashes.
String row="SELECT* FROM c_question";
final Cursor c=db.rawQuery(row, null);
c.moveToFirst();
if(c.moveToFirst())
{
do
{
tv1.setText(c.getString(1));
r0=(RadioButton)findViewById(R.id.radio0);
r0.setText(c.getString(2));
r1=(RadioButton)findViewById(R.id.radio1);
r1.setText(c.getString(3));
r2=(RadioButton)findViewById(R.id.radio2);
r2.setText(c.getString(4));
k.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
int idd=r.getCheckedRadioButtonId();
r0=(RadioButton)findViewById(idd);
String r=r0.getText().toString();
if(r.equals(c.getString(5)))
{
Toast.makeText(QuestionsOn.this, "correct!!!", 123).show();
;
} else
Toast.makeText(QuestionsOn.this, "Incorrect!!!", 123).show();
}
});
} while(c.moveToNext());
}
Output showing the last data(Question with options) I entered in DB
That's what you get when you update the save views in a loop; only the last "row" will get shown.
If you want to show a list of data from the database, you need some Adapter class in a ListView / ViewPager
and if I click any option, the app crashes...
According to the error, c.getString(5) doesn't exist, so seems like you didn't create your database with the correct number of columns.

insert in database with multiple activities

I have a problem, I created database for all activities, and in each activity I should insert information to database, so for the first activity , the insert is done , for the second activity I update the row with new insertion to complete all information and so on, my problem is that I don't know how to refer to the last row, I mean what should I do that make the update for the second activity occurs to the last row that has been insert in the first activity, do you have any suggestions ???
Well you can just use the primary key. When you insert something into the database you get as a return value the primary key. You can add this to the Intent that opens the other Activity and that way refer back to the row you previously inserted.
Edit:
I don't know if you are working with and SQLiteDatabase Object or with a ContentProvider, but in any case the code would be pretty much the same. In this example I will work directly with an SQLiteDatabase Object, even though using ContentProviders is in most cases the better alternative.
In your first Activity:
// When you perform an insert you get the id of the row which was just inserted.
long id = sqliteDatabase.insert("some_table", null, contentValues);
// The id will be -1 if an error occured
if(id >= 0) {
...
}
...
// When you start your second Activity you can add the id to the Intent
Intent intent = new Intent(getApplicationContext(), SecondActivity.class);
// The String is a tag with which you can later retrieve the id from the Intent.
// Note that you should never hardcode String like that, use constants in your app.
intent.putExtra("rowId", id);
In the onCreate Method of your second Activity you can retrieve the id:
#Override
protected void onCreate (Bundle savedInstanceState) {
// Check if the Activity has been created for the first time
if(savedInstanceState == null) {
// Retrieve the Intent with which the Activity was started
Intent intent = getIntent();
long id = intent.getLongExtra ("rowId", -1);
// If id is valid proceed
if(id >= 0) {
Cursor cursor = sqliteDatabase.query("some_table", columns, "_id = ?",
new String[] { String.valueOf(id) }, null, null, null, null);
// Check if Cursor is valid and if yes read your data.
if(cursor != null) {
...
}
}
}
}
The best way to do this would be to add a column to your database which will hold the time the row was inserted. Then when you need the latest row, query for the one with the most current time. An example SQL string would be:
SELECT * FROM my_table WHERE 1 ORDER BY time_stamp LIMIT 1

using Buttons to retrieve data from database

I have connected a database in my Android Application. Now I have created a button and when it is clicked, that should get the next data from the table of database. I have cursor and he moveToFirst() and moveToNext() methods in my code. also I have set onclick listener to my button. but in output when I click the button, its is not fetching the next data from database
heres the part of code where I have tried to set on click listener for button
c=myDbHelper.query(myDbHelper.DB_PATH +"/MainTable",null, null, null, null,null, null);
c.moveToFirst();
myques=(TextView)findViewById(R.id.question);
myrg=(RadioGroup)findViewById(R.id.rg1);
myc1=(RadioButton)findViewById(R.id.radio0);
myc2=(RadioButton)findViewById(R.id.radio1);
myc3=(RadioButton)findViewById(R.id.radio2);
myc4=(RadioButton)findViewById(R.id.radio3);
NxtQues=(Button)findViewById(R.id.button1);
myques.setText(c.getString(1));
myc1.setText(c.getString(2));
myc2.setText(c.getString(3));
myc3.setText(c.getString(4));
myc4.setText(c.getString(5));
NxtQues.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View V)
{
c.moveToNext();
}
});
what changes should I make in this code to set on click listener in a proper way.
So in your code is a few problems. At first here:
c = myDbHelper.query(myDbHelper.DB_PATH +"/MainTable", ...);
As first parameter of query() method is "raw" tablename so you can't assign there full path of your database(if it isn't real tablename...), it's wrong. Just assign MainTable like this:
c = myDbHelper.query("MainTable", null, null, null, null, null, null);
Then your logic about fetching data from database is not good at all. You assigned values to your widgets only once and no more. They never be refreshed, you need to call as many times setText() method as you want to update widget's content. Actually you don't update them.
You need to change your logic to:
#Override
public void onClick(View V) {
if (c.moveToNext()) {
myques.setText(c.getString(1));
myc1.setText(c.getString(2));
myc2.setText(c.getString(3));
myc3.setText(c.getString(4));
myc4.setText(c.getString(5));
}
}
Recommendation:
When you are using "getters" methods of Cursor, i recommend you to use column names to get columns indexes:
myques.setText(c.getString(c.getColumnIndex("columnName")));

Is Android Cursor.moveToNext() Documentation Correct?

boolean android.database.Cursor.moveToNext() documentation says:
http://developer.android.com/reference/android/database/Cursor.html#moveToNext%28%29
Move the cursor to the next row.
This method will return false if the cursor is already past the last entry in the result set.
However, my book says to do the following to extract data from a cursor:
Cursor myCursor = myDatabase.query(...);
if (myCursor.moveToFirst()) {
do {
int value = myCursor.getInt(VALUE_COL);
// use value
} while (myCursor.moveToNext());
}
Who's right? These both can't be true. If you can't see the contradiction, imagine myCursor has 1 row returned from the query. The first call to getInt() will work, but then moveToNext() will return true because it is not "already" past the last entry in the result set. So now the cursor will be past the last entry and the second call to getInt() will do something undefined.
I suspect the documentation is wrong and should instead read:
This method will return false if the cursor is "already at" the last entry in the result set.
Must the cursor be already PAST (not AT) the last entry before the moveToNext() method returns false?
No Snark Please
A simpler idiom is:
Cursor cursor = db.query(...);
while (cursor.moveToNext()) {
// use cursor
}
This works because the initial cursor position is -1, see the Cursor.getPosition() docs.
You can also find usages of cursors in the Android source code itself with this Google Code Search query. Cursor semantics are the same in SQLite database and content providers.
References: this question.
Verbatim from the API:
Returns:
whether the move succeeded.
So, it means that:
Cursor in first row -> moveToNext() -> cursor in second row -> there's no second row -> return false
If you want the details, go to the source: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.3_r1/android/database/AbstractCursor.java#AbstractCursor.moveToNext%28%29
public final boolean moveToNext() {
return moveToPosition(mPos + 1);
}
public final boolean moveToPosition(int position) {
// Make sure position isn't past the end of the cursor
final int count = getCount();
if (position >= count) {
mPos = count;
return false;
}
I think I tend to stay away from solutions that are based on a hidden assumption like: I hope that sqlite never changes it api and that a cursor will always start at the first item.
Also, I have almost always been able to replace a while statement with a for statement. So my solution, shows what I expect the cursor to start at, and avoids using a while statement:
for( boolean haveRow = c.moveToFirst(); haveRow; haveRow = c.moveToNext() ) {
...
}
why is showing that a cursor needs to start at the first row, well 6 months down the line you might be debugging your own code, and will wonder why you didn't make that explicit so you could easily debug it.
It appears to be down to the Android implementation of AbstractCursor and it remains broken in Jellybean.
I implemented the following unit test to demonstrate the problem to myself using a MatrixCursor:
#Test
public void testCursor() {
MatrixCursor cursor = new MatrixCursor(new String[] { "id" });
for (String s : new String[] { "1", "2", "3" }) {
cursor.addRow(new String[] { s });
}
cursor.moveToPosition(0);
assertThat(cursor.moveToPrevious(), is(true));
cursor.moveToPosition(cursor.getCount()-1);
assertThat(cursor.moveToNext(), is(true));
assertThat(cursor.moveToPosition(c.getCount()), is(true));
assertThat(cursor.moveToPosition(-1), is(true));
}
All assertions fail, contrary to the documentation for moveToNext, moveToPrevious and moveToPosition.
Reading the code at API 16 for AbstractCursor.moveToPosition(int position) it appears to be intentional behaviour, ie the methods explicitly return false in these cases, contrary to the documentation.
As a side note, since the Android code sat on existing devices in the wild cannot be changed, I have taken the approach of writing my code to match the behaviour of the existing Android implementation, not the documentation. ie. When implementing my own Cursors / CursorWrappers, I override the methods and write my own javadoc describing the departure from the existing documentation. This way, my Cursors / CursorWrappers remain interchangeable with existing Android cursors without breaking run-time behaviour.
Cursor.moveToNext() returning a boolean is only useful if it will not move the cursor past the last entry in the data set. Thus, I have submitted a bug report on the documentation's issue tracker.
https://issuetracker.google.com/issues/69259484
It reccomends the following sentence:
"This method will return false if the current (at time of execution) entry is the last entry in the set, and there is no next entry to be had."

Categories

Resources