Spurious Transaction errors in AsyncTask - java

I'm getting spurious errors when trying to doInBackground of an AsyncTask which I initiate in MainActivity when the user clicks btnSearch.
Here's the part of doInBackground where I might get no error, might get error after 1 transaction, might get error at record 23001 after 23 transactions. I got returnCode < 0 after trying to insertWord.
private class LoadDatabase extends AsyncTask<Object, Integer, Void
{
protected Void doInBackground(Object[] params)
{
...
my_openDb("DatabaseConnector.LoadDatabase.doInBackground");
while(scDict.hasNext())
{
int kTransactions = 0;
try
{
mDatabase.beginTransaction();
while(kTransactions < MAX_TRANSACTIONS && scDict.hasNext())
{
s = scDict.next();
count++;
long returnCode = insertWord(s); // error here **********
if(returnCode < 0)
{
if(debug) Log.w("`````Abort--can't add <",s + "> at " + count + " with RC = " + returnCode + " and KT = " + kTransactions );
my_closeDb("DatabaseConnector.LoadDatabase.doInBackground DISASTER");//$$
System.exit(0);
}
++ kTransactions;
}
mDatabase.setTransactionSuccessful();
}
catch(Exception e){ Log.w("`````", "Exception " + e.toString()); }
finally
{
mDatabase.endTransaction();
publishProgress((Integer) (int) s.charAt(0));
}
}
my_closeDb("doInBackground outer while ended");
Method insertWord:
long insertWord(String _word)
{
long r = -1;
ContentValues newWord = new ContentValues();
newWord.put(WORD_COLUMN_NAME, _word);
try {
// r = mDatabase.insert (TABLE_NAME,null,newWord);
r = mDatabase.insertWithOnConflict(TABLE_NAME,null,newWord,SQLiteDatabase.CONFLICT_REPLACE);
return r;
} catch (Exception e) {
if(debug)Log.w("DBC", "insert failed for <" + _word + ">");
if(debug)Log.w("DBC", "exception " + e.toString());
my_closeDb(DatabaseConnector.insertWord");
}
return -1 ; // -1 if can't insert
}
The commented out line always worked in the past, until major overhaul to simplify code. (GREAT idea.)(NOT!) I got the idea of insertWithOnConflict from https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html where they say insertWithOnConflict is the General method for inserting a row into the database.
Of course I got insert from a text book. AS says it's a convenience method for inserting a row into the database. But it does lack the algorithm for resolving conflicts. There shouldn't be any conflicts. I made the change to insertWithOnConflict when I got errors with insert.
That was probably my first warning that I was on thin ice.
I call DatabaseConnector from MainActivity, which calls inner classes: DatabaseHelper, which calls LoadDatabase and then doInBackground.
I'm not sure I should have an AsyncTask so directly connected to MainActivity because of Answers and comments suggesting concurrence issues might be a problem.
Another suggestion was to put the AsyncTask inside MainActivity. That would mean I'd have to move classes DatabaseConnector and its inner classes there, too. If it all seems overly-complicated, I just followed the big example in a textbook. But you must connect the database to the activity, and you do need help, and if the task MUST run in the background, so it must be AsyncTask, as I understand it.
Call from MainActivity:
dbc = new DatabaseConnector(MainActivity.this, getAssets(), databaseFileExists());
In DatabaseConnector:
public DatabaseConnector(Context _context, AssetManager _assets, boolean dbExists)
{
mContext = _context;
mDbOpenHelper = new DbOpenHelper(_context, DATABASE_NAME, null, 1);
SQLiteDatabase db = mDbOpenHelper.getWritableDatabase();
if( ! dbExists)
createAndOrFillDb(db);
}
createAndOrFillDb:
void createAndOrFillDb(SQLiteDatabase db)
{
...
db.execSQL("CREATE TABLE " + TABLE_NAME + "(" + WORD_COLUMN_NAME + " TEXT primary key );")
//...
cursor = db.query(TABLE_NAME, mColumns, null, null, null, null, WORD_COLUMN_NAME);
//...
LoadDatabase
__loadDb;
__loadDb = new LoadDatabase();
__loadDb.execute((Object[]) null);
}
So my questions are
(1) Do you think I'm getting spurious results during transactions in doInBackground because of it being in a class separate from MainActivity and if so, what can I do about it?
(2) Do you think I should just stick 1200 lines of code into MainActivity, resulting in a 2200-line monster so I won't have to worry about concurrency?
(3) Maybe initiate doInBackground from a thread separate from UI? (No clue how to control that, but I'm willing to try.)
(4) What else might you suggest?

Related

SQL Update in another class

Thanks for helping me out with my problem.
So I tried to insert a SQL Update using a method in a class named Functions, the main program doesn't give any error, and whenever I use the function, it just don't change anything in the SQL table after refreshing.
Here is my updateSQL method in class Functions :
public static void updateSQL(String sql, String updatenames[])
{
Connection connected = null;
PreparedStatement prepared = null;
connected = connexionmysql.connexionDB();
try
{
prepared = connected.prepareStatement(sql);
for(int i = 1; i < updatenames.length+1 ; i++) {
prepared.setString(i, updatenames[i-1]);
}
prepared.execute();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
And here is how I call this function in my main class :
String datan[] = { inputPASS, type1, inputEMAIL, inputNAME };
Functions.updateSQL("UPDATE users SET userPASS = ? ,userTYPE = ? ,userEMAIL = ? WHERE userNAME = ? ", datan);
I did solve the problem. The functions I did write are working as expected, but the problem was in the way I was calling the method.
Instead of calling the function in a static way, I was calling it in an "usual" way so Eclipse didn't give me any error in that line.

cursor is crashing with CursorIndexOutOfBoundsException

My cursor is crashing my application with the android database error.
CursorIndexOutOfBoundsException: Index -1 requested, with a size of 1
I made another much less optimized slider that scans my database and I find the good value.
public Cursor getAllDataTableStaffDatabase(String table_name){
this.open();
Cursor result =this.mDb.rawQuery("SELECT * FROM " + table_name,null);
return result;// fonctionne très bien
}
public String findNameOfStaffBymail(String mail) {
String sql = " SELECT * FROM " + DatabaseStaffHandler.STAFF_TABLE_NAME + " WHERE " + DatabaseStaffHandler.STAFF_MAIL + " = ? ";
Cursor result = super.mDb.rawQuery(sql, new String[]{mail});
Cursor data = super.getAllDataTableStaffDatabase(DatabaseStaffHandler.STAFF_TABLE_NAME);
String test = result.getString(1); //error
while (data.moveToNext()) {
if (data.getString(3).equals(mail)) {
viewAll();
return data.getString(1);
}
}
}
I would like to retrieve the value name that corresponds to the email address.
This usually happens when you do not have the data in your Cursor and you are still trying to access the data. It is similar to the ArrayIndexOutOfBoundsException. I found nothing wrong with your query so far. However, I think you might consider adding null checking in your code which will prevent your application from crashing. Especially in the while loop, you need to put a null check in the condition.
And you need to use the moveToFirst function wherever necessary.
public Cursor getAllDataTableStaffDatabase(String table_name) {
this.open();
Cursor result = this.mDb.rawQuery("SELECT * FROM " + table_name,null);
return result;
}
public String findNameOfStaffBymail(String mail) {
String sql = " SELECT * FROM " + DatabaseStaffHandler.STAFF_TABLE_NAME + " WHERE " + DatabaseStaffHandler.STAFF_MAIL + " = ? ";
Cursor result = super.mDb.rawQuery(sql, new String[]{mail});
Cursor data = super.getAllDataTableStaffDatabase(DatabaseStaffHandler.STAFF_TABLE_NAME);
// Add a null checking here.
if (result != null) {
result.moveToFirst();
String test = result.getString(1);
}
if(data != null) data.moveToFirst();
while (data != null) {
if (data.getString(3).equals(mail)) {
viewAll();
return data.getString(1);
}
data.moveToNext();
}
}
Hope that solves your problem.
Thank you for your reply. I found my problem thanks to you. I wonder if the cursor does not boot at the end. But it is good practice to check if received is not null. Thank you and have a nice day

Updating a table when clicking on an element from another table

I need to show elements on a table depending on the element (Person) clicked on another table. The problem is that, using a Service, if the user clicks on two elements of the first table very quickly, the data of the two elements is showed in the table, and I only want to show the data from the last one clicked. Hope you can help me.
Here is my code:
personTable.getSelectionModel().selectedItemProperty().addListener(
(observable, oldValue, newValue) -> {
try {
contactoTable.setPlaceholder(new Label("Cargando..."));
showPersonDetails(newValue);
} catch (SQLException ex) {
Logger.getLogger(PersonOverviewController.class.getName()).log(Level.SEVERE, null, ex);
}
});
And showPersonDatails:
contactoTable.setVisible(true);
contactoTable.getItems().clear();
firstNameLabel.setText(person.getFirstName());
lastNameLabel.setText(person.getLastName());
mailLabel.setText(person.getMail());
phoneLabel.setText(person.getPhone());
descriptionLabel.setText(person.getDescription());
service = new Service<Void>() {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
//Background work
DBManager db = new DBManager();
String query = "SELECT * FROM eventos";
ResultSet r = db.executeSelect(query);
contactoTable.getItems().clear();
contactoData.clear();
while (r.next()) {
String q = "SELECT * FROM " + r.getString("Nombre").replace(" ", "_") + " WHERE Nombre = '" + person.getFirstName() + "' AND Apellidos = '" + person.getLastName() + "' AND Correo = '" + person.getMail() + "'";
ResultSet result = db.executeSelect(q);
while (result.next()) {
contactoData.add(new Row(r.getString("Nombre"), result.getString("Asistencia")));
}
}
final CountDownLatch latch = new CountDownLatch(1);
Platform.runLater(() -> {
try {
//FX Stuff done here
contactoTable.setPlaceholder(new Label("No invitado a ningún evento"));
contactoTable.setItems(contactoData);
} finally {
latch.countDown();
}
});
latch.await();
//Keep with the background work
return null;
}
};
}
};
service.start();
You are referencing the same data list (contactoData) from multiple threads, with apparently no synchronization on the list. If the user selects two different items in rapid succession, you launch a service for each one, each service running its task in a different thread. Consequently you have no control over the order the two different threads perform their (multiple) manipulations on contactoData. For example, it is possible (even probable) that the order for two services executing asynchronously is:
First service clears the list
Second service clears the list
First service adds elements to the list
Second service adds elements to the list
and in this case the list contains elements generated by both services, not just one of them.
So you should have your tasks operate on, and return, a new list they create. Then process that list on the FX Application Thread.
It's also not clear why you need a service here, as you only seem to ever use each service once. You may as well just use a task directly.
You also probably want to ensure that the last selection is the one displayed. Since the tasks are running asynchronously, it's possible that if two tasks were started in quick succession, the second would complete before the first. This would result in the second selection being displayed, and then the first selection replacing it. You can avoid this by doing the UI update in an onSucceeded handler, and canceling any current task when you start a new one (thus preventing the currently-executing task from invoking its onSucceeded handler).
Finally, it's really not clear to me why you are making the task wait until the UI is updated.
Here is an updated version of your code:
private Task<List<Row>> updateContactTableTask ;
// ...
private void showPersonDetails(Person person) {
contactoTable.getItems().clear();
firstNameLabel.setText(person.getFirstName());
lastNameLabel.setText(person.getLastName());
mailLabel.setText(person.getMail());
phoneLabel.setText(person.getPhone());
descriptionLabel.setText(person.getDescription());
if (updateContactTableTask != null && updateContactTableTask.isRunning()) {
updateContactTableTask.cancel();
}
updateContactTableTask = new Task<List<Row>>() {
#Override
protected List<Row> call() throws Exception {
List<Row> resultList = new ArrayList<>() ;
//Background work
DBManager db = new DBManager();
String query = "SELECT * FROM eventos";
ResultSet r = db.executeSelect(query);
// quit if we got canceled here...
if (isCancelled()) {
return resultList;
}
while (r.next() && ! isCancelled()) {
// Note: building a query like this is inherently unsafe
// You should use a PreparedStatement in your DBManager class instead
String q = "SELECT * FROM " + r.getString("Nombre").replace(" ", "_") + " WHERE Nombre = '" + person.getFirstName() + "' AND Apellidos = '" + person.getLastName() + "' AND Correo = '" + person.getMail() + "'";
ResultSet result = db.executeSelect(q);
while (result.next()) {
resultList.add(new Row(r.getString("Nombre"), result.getString("Asistencia")));
}
}
return resultList ;
}
};
updateContactTableTask.setOnSucceeded(e -> {
// not really clear you still need contactoData, but if you do:
contactoData.setAll(updateContactTableTask.getValue());
contactoTable.setPlaceholder(new Label("No invitado a ningún evento"));
contactoTable.setItems(contactoData);
});
updateContactTableTask.setOnFailed(e -> {
// handle database errors here...
});
new Thread(updateContactTableTask).start();
}
As an aside, it's not clear to me if, and if so, how, you are closing your database resources. E.g. the result sets never seem to get closed. This could cause resource leaks. However this is incidental to the question (and relies on knowing how your DBManager class is implemented), so I won't address it here.

Peculiar deadlock related to logging framework

I have a GUI-based application that takes in a file and displays it to the user in a table format, gets some input in the form of column annotations and a bunch of parameters. Then it parses the file accordingly and initiates an "analysis".
I just found a deadlock, one I have not encountered before.
Found one Java-level deadlock:
=============================
"RMI TCP Connection(5)-130.235.214.23":
waiting to lock monitor 0x00007fac650875e8 (object 0x0000000793267298, a java.util.logging.ConsoleHandler),
which is held by "AWT-EventQueue-0"
"AWT-EventQueue-0":
waiting to lock monitor 0x00007fac65086b98 (object 0x00000006c00dd8d0, a java.io.PrintStream),
which is held by "SwingWorker-pool-1-thread-3"
"SwingWorker-pool-1-thread-3":
waiting to lock monitor 0x00007fac65087538 (object 0x00000006c001db48, a java.awt.Component$AWTTreeLock),
which is held by "AWT-EventQueue-0"
Essentially there is a parsing error and trying to log it hangs the application altogether. Interestingly logging appears to work normally before and after that particular step..
Here's the part of the code that's relevant for the analysis task:
// Activate progress indicator
frame.getMainFrame().activateInfiGlass();
SwingWorker<Map<Analyte,AnalysisResult>, Void> worker = new SwingWorker<Map<Analyte,AnalysisResult>, Void>() {
#Override
protected Map<Analyte,AnalysisResult> doInBackground() {
try {
// register parameters
param.addParam(AnalysisParams.value_key,descPanel.getValueTypeComboIndex());
param.addParam(AnalysisParams.sepchar_key,descPanel.getSepCharComboIndex());
paramPanel.registerParams();
StringBuilder sb = new StringBuilder("Data preview completed, initiating analysis...");
sb.append(System.lineSeparator())
.append("... column annotations: ")
.append(Arrays.toString(annots));
logger.info(sb.toString() + System.lineSeparator());
// Create dataset; to be passed on to SwingWorker which will
// execute the analysis
ds = new Dataset();
String[] line;
for (int i=0; i < data.length; i++){
line = data[i];
// If ignore button is clicked, skip row..
if(!(Boolean) table.getValueAt(i, 0))
ds.addRow(line, annots); // <-- This step is where the parsing exception occurs
}
System.out.println("Dataset parsed...");
logger.info("Dataset parsing complete "
+ System.lineSeparator()
+ ds.toString()
+ System.lineSeparator());
visualizeDataset();
conserv = new ConcurrencyService(ds, dbMan);
conserv.serve();
} catch (InterruptedException e) {
logger.severe("Concurrency service interrupted"
+ System.lineSeparator()
+ DebugToolbox.getStackTraceAsString(e)
+ System.lineSeparator());
System.err.println("Interrupt exception!!");
}
return conserv.getAnalyzedPaths();
}
#Override
protected void done() {
try{
results = get();
visualizeResults();
}
catch (InterruptedException ignore) {}
catch (java.util.concurrent.ExecutionException e) {
String why = null;
Throwable cause = e.getCause();
if (cause != null) {
why = cause.getMessage();
} else {
why = e.getMessage();
}
System.err.println("Error analysing data: " + why);
} catch (SQLException e) {
e.printStackTrace();
}
logger.info("#DEBUG: Conserv should have been terminated by now..." + System.lineSeparator());
frame.getMainFrame().deactivateInfiGlass();
DebugToolbox.stopExecTimer();
}
};
worker.execute();
}});
The parsing of the values happens in an instance of Dataset, using method addRow(). The following piece of code shows the way the parsing error is handled
public double valueToIntensity(String val){
if(val.equalsIgnoreCase(""))
return missingVal;
try{
double d = Double.parseDouble(val);
switch(valType){
case RAW: break;
case LOG2: d = StrictMath.pow(2,d); break;
case LOGN: d = StrictMath.pow(StrictMath.E, d); break;
case LOG10: d = StrictMath.pow(10,d); break;
default: throw new RuntimeException("Unrecognized value type");
}
if(Double.isInfinite(d)){
StringBuilder msg = new StringBuilder("Double precision overflow occurred: 'd' is infinite!!");
msg.append(System.lineSeparator())
.append("chosen value scale is ").append(valType)
.append(System.lineSeparator())
.append("value = ").append(val);
logger.severe(msg.toString() + System.lineSeparator());
System.err.println("Data parsing error!!" +
"Please make sure that you have selected the correct scale...");
System.exit(FeverMainFrame.exitCodes.get(this.getClass()));
}
else
return d;
} catch (NumberFormatException e){
System.err.println("Data parsing error!!");
// THE FOLLOWING LINE IS WHERE DEADLOCK OCCURS
logger.severe("Expected: string representation of a numerical value, "
+ "Found: " + val + System.lineSeparator());
System.err.println("Please make sure the datafile does not include any strings "
+ "like 'N/A' or '-' for denoting missing values.");
System.exit(FeverMainFrame.exitCodes.get(this.getClass()));
}
// TODO: This should never happen!
throw new RuntimeException("Assertion failed during dataset parsing...");
}
If I remove the values that are causing the parsing error, without changing anything else, both the logging framework and the rest of application runs as expected.
I would really appreciate any insight as to what is going on in this particular case.
Absent a complete example, verify that your implementation of doInBackground() does not attempt to update any GUI component or model. Instead, publish() interim results and process() them on the EDT as they become available. A complete example is shown here.

Strange NullPointerException error in android when using custom suggestions provider

I am trying to make custom search suggestions in my app. I’ve started from documentation and Searchable dictionary example. However, this example isn’t so good for me so I’ve started with some tests to find out exactly how to make it, because there is not much tutorials in the Internet also.
Generally my app has right now 2 databases – one normal and second with less number of columns – FTS3. What I would like to achieve is to connect suggestions provider to this FTS3 table.
What I was trying to do was to now was, using simple function, return in suggestions whole DB (around 200 records) after typing any letter in search box. I know about limit 50 records, but I don’t think it is the problem.
This are fragments from Provider’s code. What I found out, that when you type in text, provider goes to option SEARCH_SUGGEST:
// UriMatcher stuff
private static final int SEARCH_WORDS = 0;
private static final int GET_WORD = 1;
private static final int SEARCH_SUGGEST = 2;
private static final int REFRESH_SHORTCUT = 3;
private static final UriMatcher mUriMatcher = buildUriMatcher();
/**
* Builds up a UriMatcher for search suggestion and shortcut refresh queries.
*/
private static UriMatcher buildUriMatcher() {
Log.d(TAG,"urimatcher");
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
// to get definitions...
matcher.addURI(AUTHORITY, "mydb", SEARCH_WORDS);
matcher.addURI(AUTHORITY, "mydb/#", GET_WORD);
// to get suggestions...
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST);
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST);
return matcher;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
switch (mUriMatcher.match(uri)) {
case SEARCH_SUGGEST:
Log.d(TAG,"SEARCH_SUGGEST");
if (selectionArgs == null) {
throw new IllegalArgumentException(
"selectionArgs must be provided for the Uri: " + uri);
}
return getSuggestions(selectionArgs[0]);
case SEARCH_WORDS:
Log.d(TAG,"SEARCH_WORDS");
if (selectionArgs == null) {
throw new IllegalArgumentException(
"selectionArgs must be provided for the Uri: " + uri);
}
return search(selectionArgs[0]);
case GET_WORD:
Log.d(TAG,"GET_WORD");
return null;
default:
Log.d(TAG,"default");
throw new IllegalArgumentException("Unknown Uri: " + uri);
}
}
private Cursor getSuggestions(String query) {
String[] columns = { MyDBAdapter.KEY_TITLE,MyDBAdapter.KEY_ID};
Log.d(TAG,"query1: " + query);
try{
Cursor tmp = MyDB.getAllEntriesFTS(false, columns,
null, null, null, null, MyDBAdapter.KEY_TITLE, null, query);
Log.d(TAG,"cursor: " + Integer.toString(tmp.getCount()));
}
catch(Exception e)
{
Log.d(TAG,e.toString());
}
return tmp;
}
In getSuggestions I put code that should generally work, but it doesn’t. Doesn’t work only when used here. When I used it in other activity to get cursor for listview everything was fine. Here it returns my NullPointerException.
So getting deeper I put also some Log tags in getAllEntriesFTS method and this method looks like this:
public Cursor getAllEntriesFTS(boolean distinct, String[] result_columns,
String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit, String query) {
Log.d(TAG,"query db: " + query);
String[] columns = { MyDBAdapter.KEY_TITLE, MyDBAdapter.KEY_ID};
Log.d(TAG,"columns: " + Integer.toString(result_columns.length));
Cursor allRows = null;
try{
allRows = db.query(distinct, DATABASE_TABLE_FTS, columns,
null, null, null, null, MyDBAdapter.KEY_TITLE, null);
Log.d(TAG,"OK");
}
catch(Exception e)
{
Log.d(TAG, e.toString());//it always goes there with NullPointerExceptionwhen used in provider
}
Log.d(TAG,Integer.toString(allRows.getCount()));
return allRows;
}
So, generalny speaking it should return cursor to whole DB, but instead it throws In place where it shouldn’t NullPointerException.
Can someone please tell me what am I doing wrong and how it should be done?
Thank's to JB Nizet I was able to find my mistake. I was thinking I've studied Google's example good, but I was wrong.
The problem was lack of database open before cursor call. It should look like this:
private Cursor getSuggestions(String query) {
String[] columns = { MyDBAdapter.KEY_TITLE,MyDBAdapter.KEY_ID};
Log.d(TAG,"query1: " + query);
try{
MyDB.open();
Cursor tmp = MyDB.getAllEntriesFTS(false, columns,
null, null, null, null, MyDBAdapter.KEY_TITLE, null, query);
MyDB.close();
Log.d(TAG,"cursor: " + Integer.toString(tmp.getCount()));
}
catch(Exception e)
{
Log.d(TAG,e.toString());
}
return tmp;
}
Thank you all for showing me it.

Categories