From d81d332d146b7d2708eefc0ce47441558d9cc757 Mon Sep 17 00:00:00 2001 From: bellerbrock Date: Mon, 13 May 2024 21:24:41 -0400 Subject: [PATCH 1/3] add field import error alerts with error details --- .../tracker/async/ImportRunnableTask.java | 72 +++-- .../tracker/objects/FieldFileObject.java | 304 ++++++++++-------- app/src/main/res/values/strings.xml | 7 + 3 files changed, 228 insertions(+), 155 deletions(-) diff --git a/app/src/main/java/com/fieldbook/tracker/async/ImportRunnableTask.java b/app/src/main/java/com/fieldbook/tracker/async/ImportRunnableTask.java index b8f815476..c437f5a6d 100644 --- a/app/src/main/java/com/fieldbook/tracker/async/ImportRunnableTask.java +++ b/app/src/main/java/com/fieldbook/tracker/async/ImportRunnableTask.java @@ -1,10 +1,13 @@ package com.fieldbook.tracker.async; +import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; +import android.content.DialogInterface; import android.content.SharedPreferences; import android.os.AsyncTask; import android.text.Html; +import android.util.Log; import androidx.preference.PreferenceManager; @@ -33,8 +36,10 @@ public class ImportRunnableTask extends AsyncTask { int lineFail = -1; boolean fail; + String failMessage; boolean uniqueFail; boolean containsDuplicates = false; + private static final String TAG = "ImportRunnableTask"; public ImportRunnableTask(Context context, FieldFileObject.FieldFileBase fieldFile, int idColPosition, String unique, String primary, String secondary) { @@ -82,9 +87,11 @@ protected Integer doInBackground(Integer... params) { } if (mFieldFile.hasSpecialCharacters()) { + Log.d(TAG, "doInBackground: Special characters found in file column names"); return 0; } + mFieldFile.open(); String[] data; String[] columns = mFieldFile.readNext(); @@ -107,7 +114,7 @@ protected Integer doInBackground(Integer... params) { //populate an array of indices that have a non empty column //later we will only add data rows with the non empty columns //also find the unique/primary/secondary indices - //later we will skip the rows if these are not present + //later we will return an error if these are not present if (!columns[i].isEmpty()) { if (!nonEmptyColumns.contains(columns[i])) { @@ -136,9 +143,7 @@ protected Integer doInBackground(Integer... params) { //start iterating over all the rows of the csv file only if we found the u/p/s indices if (uniqueIndex > -1 && primaryIndex > -1 && secondaryIndex > -1) { - int line = 0; - try { while (true) { data = mFieldFile.readNext(); @@ -164,6 +169,15 @@ protected Integer doInBackground(Integer... params) { controller.getDatabase().createFieldData(studyId, nonEmptyColumns, nonEmptyData); + } else { + fail = true; + if (data[uniqueIndex].isEmpty()) { + failMessage = mContext.get().getString(R.string.import_runnable_create_field_missing_unique, unique, line); + } else if (data[primaryIndex].isEmpty()) { + failMessage = mContext.get().getString(R.string.import_runnable_create_field_missing_primary, primary, line); + } else if (data[secondaryIndex].isEmpty()) { + failMessage = mContext.get().getString(R.string.import_runnable_create_field_missing_secondary, secondary, line); + } } } @@ -171,8 +185,10 @@ protected Integer doInBackground(Integer... params) { } controller.getDatabase().setTransactionSuccessfull(); + Log.d(TAG, "doInBackground: Field data created successfully for study ID: " + studyId); } catch (Exception e) { + Log.e(TAG, "doInBackground: Exception at line " + line + ", Error: " + e.getMessage(), e); lineFail = line; @@ -185,6 +201,8 @@ protected Integer doInBackground(Integer... params) { controller.getDatabase().endTransaction(); } + } else { + Log.d(TAG, "doInBackground: Required indices not found. UniqueIndex: " + uniqueIndex + ", PrimaryIndex: " + primaryIndex + ", SecondaryIndex: " + secondaryIndex); } @@ -198,7 +216,7 @@ protected Integer doInBackground(Integer... params) { } catch (Exception e) { e.printStackTrace(); fail = true; - + failMessage = mContext.get().getString(R.string.import_runnable_create_field_data_failed); controller.getDatabase().close(); controller.getDatabase().open(); } @@ -215,24 +233,25 @@ protected void onPostExecute(Integer result) { if (dialog.isShowing()) dialog.dismiss(); - if (fail | uniqueFail | mFieldFile.hasSpecialCharacters()) { + // Display user feedback in an alert dialog + if (context != null && (uniqueFail || mFieldFile.hasSpecialCharacters())) { + String errorMessage = mFieldFile.getLastError(); + showAlertDialog(context, "Unable to Import", errorMessage); + } else if (context != null && fail ) { + showAlertDialog(context, "Unable to Import", failMessage); + } else if (containsDuplicates) { + showAlertDialog(context, "Import Warning", context.getString(R.string.import_runnable_duplicates_skipped)); + } + + if (fail || uniqueFail || mFieldFile.hasSpecialCharacters()) { controller.getDatabase().deleteField(result); SharedPreferences.Editor ed = preferences.edit(); ed.putString(GeneralKeys.FIELD_FILE, null); ed.putBoolean(GeneralKeys.IMPORT_FIELD_FINISHED, false); ed.apply(); - } - if (containsDuplicates) { - Utils.makeToast(context, context.getString(R.string.import_runnable_duplicates_skipped)); - } - if (fail) { - Utils.makeToast(context, context.getString(R.string.import_runnable_create_field_data_failed, lineFail)); - //makeToast(getString(R.string.import_error_general)); - } else if (uniqueFail && context != null) { - Utils.makeToast(context,context.getString(R.string.import_error_unique)); - } else if (mFieldFile.hasSpecialCharacters()) { - Utils.makeToast(context,context.getString(R.string.import_error_unique_characters_illegal)); } else { + Log.d(TAG, "onPostExecute: Import successful. Field setup for ID: " + result); + SharedPreferences.Editor ed = preferences.edit(); CollectActivity.reloadData = true; @@ -262,11 +281,26 @@ protected void onPostExecute(Integer result) { } private boolean verifyUniqueColumn(FieldFileObject.FieldFileBase fieldFile) { - HashMap check = fieldFile.getColumnSet(idColPosition); - if (check.isEmpty()) { + HashMap result = fieldFile.getColumnSet(unique, idColPosition); + if (result == null) { return false; } else { - return controller.getDatabase().checkUnique(check); + return controller.getDatabase().checkUnique(result); } } + + private void showAlertDialog(Context context, String title, String message) { + new AlertDialog.Builder(context, R.style.AppAlertDialog) + .setTitle(title) + .setMessage(message) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .setCancelable(false) + .show(); + } + } diff --git a/app/src/main/java/com/fieldbook/tracker/objects/FieldFileObject.java b/app/src/main/java/com/fieldbook/tracker/objects/FieldFileObject.java index 8848fb831..9bc1c2f67 100644 --- a/app/src/main/java/com/fieldbook/tracker/objects/FieldFileObject.java +++ b/app/src/main/java/com/fieldbook/tracker/objects/FieldFileObject.java @@ -1,27 +1,22 @@ package com.fieldbook.tracker.objects; -import static org.apache.poi.ss.usermodel.Cell.CELL_TYPE_BOOLEAN; -import static org.apache.poi.ss.usermodel.Cell.CELL_TYPE_NUMERIC; -import static org.apache.poi.ss.usermodel.Cell.CELL_TYPE_STRING; - import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.provider.OpenableColumns; +import android.util.Log; import androidx.annotation.Nullable; +import com.fieldbook.tracker.R; import com.fieldbook.tracker.utilities.CSVReader; +import com.fieldbook.tracker.utilities.StringUtil; -import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.ss.usermodel.FormulaEvaluator; -import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFCell; -import org.apache.poi.xssf.usermodel.XSSFFormulaEvaluator; import org.apache.poi.xssf.usermodel.XSSFRow; import org.apache.poi.xssf.usermodel.XSSFSheet; -import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.IOException; import java.io.InputStream; @@ -31,9 +26,6 @@ import java.util.Iterator; import java.util.UUID; -import jxl.Workbook; -import jxl.WorkbookSettings; - //TODO when merged with xlsx edit getColumnSet public class FieldFileObject { public static FieldFileBase create(final Context ctx, final Uri path, @@ -64,6 +56,7 @@ public static String getExtension(final String path) { public abstract static class FieldFileBase { boolean openFail; boolean specialCharactersFail; + private String lastErrorMessage = ""; private final Uri path_; private final Context ctx; @@ -74,6 +67,38 @@ public abstract static class FieldFileBase { specialCharactersFail = false; } + protected void setLastError(String message) { + this.lastErrorMessage = message; + Log.e("FieldFileBase", message); + } + + public String getLastError() { + return this.lastErrorMessage; + } + + // Helper method to check unique values + protected boolean checkUnique(HashMap check, String value, String columnLabel, int rowIndex) { + + if (value == null || value.isEmpty()) { + setLastError(ctx.getString(R.string.import_runnable_create_field_missing_unique, columnLabel, rowIndex + 1)); + return false; + } + + if (check.containsKey(value)) { + setLastError(ctx.getString(R.string.import_runnable_create_field_duplicate_unique, value, rowIndex + 1)); + return false; + } + + if (value.contains("/") || value.contains("\\")) { + specialCharactersFail = true; + setLastError(ctx.getString(R.string.import_runnable_create_field_special_characters, value, rowIndex + 1)); + return false; + } + + check.put(value, value); + return true; + } + public final InputStream getInputStream() { try { return this.ctx.getContentResolver().openInputStream(this.path_); @@ -165,9 +190,7 @@ public boolean getOpenFailed() { abstract public String[] getColumns(); - // return {column name: column name} - // if columns are duplicated, return an empty HshMap - abstract public HashMap getColumnSet(int idColPosition); + abstract public HashMap getColumnSet(String unique, int idColPosition); // read file abstract public void open(); @@ -208,39 +231,34 @@ public String[] getColumns() { } } - public HashMap getColumnSet(int idColPosition) { + public HashMap getColumnSet(String columnLabel, int idColPosition) { + HashMap check = new HashMap<>(); try { openFail = false; - HashMap check = new HashMap<>(); InputStreamReader isr = new InputStreamReader(super.getInputStream()); CSVReader cr = new CSVReader(isr); String[] columns = cr.readNext(); + int rowIndex = 0; while (columns != null) { columns = cr.readNext(); - + rowIndex++; if (columns != null) { - String unique = columns[idColPosition]; - if (!unique.isEmpty()) { - if (check.containsKey(unique)) { - cr.close(); - return new HashMap<>(); - } else { - check.put(unique, unique); - } - - if (unique.contains("/") || unique.contains("\\")) { - specialCharactersFail = true; - } + if (!checkUnique(check, columns[idColPosition], columnLabel, rowIndex)) { + close(); + return null; // Return null to indicate an error has occurred } } } - return check; - } catch (Exception n) { + } catch (Exception e) { openFail = true; - n.printStackTrace(); - return new HashMap<>(); + e.printStackTrace(); + setLastError("Failed to process file: " + e.getMessage()); + return null; + } finally { + close(); } + return check; } public void open() { @@ -278,6 +296,8 @@ public static class FieldFileExcel extends FieldFileBase { private Workbook wb; private int current_row; + DataFormatter formatter = new DataFormatter(); + FieldFileExcel(final Context ctx, final Uri path) { super(ctx, path); } @@ -297,63 +317,92 @@ public boolean isOther() { public String[] getColumns() { try { openFail = false; - WorkbookSettings wbSettings = new WorkbookSettings(); - wbSettings.setUseTemporaryFileDuringWrite(true); - InputStream is = super.getInputStream(); if (is != null) { - wb = Workbook.getWorkbook(super.getInputStream(), wbSettings); - String[] importColumns = new String[wb.getSheet(0).getColumns()]; - - for (int s = 0; s < wb.getSheet(0).getColumns(); s++) { - importColumns[s] = wb.getSheet(0).getCell(s, 0).getContents(); + wb = new XSSFWorkbook(is); + Sheet sheet = wb.getSheetAt(0); + Row headerRow = sheet.getRow(0); + if (headerRow == null) return new String[0]; + + String[] importColumns = new String[headerRow.getLastCellNum()]; + for (int cn = 0; cn < headerRow.getLastCellNum(); cn++) { + Cell cell = headerRow.getCell(cn, Row.RETURN_BLANK_AS_NULL); + importColumns[cn] = (cell == null) ? "" : formatter.formatCellValue(cell); } return importColumns; } - } catch (Exception ignore) { openFail = true; - return new String[0]; } - return new String[0]; } - public HashMap getColumnSet(int idColPosition) { + @Override + public HashMap getColumnSet(String columnLabel, int idColPosition) { HashMap check = new HashMap<>(); + try { + open(); + Sheet sheet = wb.getSheetAt(0); + int totalRows = sheet.getLastRowNum(); - for (int s = 0; s < wb.getSheet(0).getRows(); s++) { - String value = wb.getSheet(0).getCell(idColPosition, s).getContents(); + for (int rowIndex = 0; rowIndex <= totalRows; rowIndex++) { + Row row = sheet.getRow(rowIndex); + Cell cell = row.getCell(idColPosition, Row.RETURN_BLANK_AS_NULL); + String value = cell == null ? "" : formatter.formatCellValue(cell); - if (!value.isEmpty()) { - if (check.containsKey(value)) { - return new HashMap<>(); - } else { - check.put(value, value); + if (value.isEmpty() && isRowEmpty(row)) { + continue; // Skip the row if the specific cell is empty and the whole row is empty } - if (value.contains("/") || value.contains("\\")) { - specialCharactersFail = true; + if (!checkUnique(check, value, columnLabel, rowIndex)) { + return null; } } + } catch (Exception e) { + setLastError("Failed to process Excel file: " + e.getMessage()); + return null; + } finally { + close(); } return check; } + private boolean isRowEmpty(Row row) { + if (row == null) return true; + for (int cellNum = row.getFirstCellNum(); cellNum < row.getLastCellNum(); cellNum++) { + Cell cell = row.getCell(cellNum, Row.RETURN_BLANK_AS_NULL); + if (cell != null && cell.getCellType() != Cell.CELL_TYPE_BLANK) { + return false; + } + } + return true; + } + public void open() { current_row = 0; } public String[] readNext() { - if (current_row >= wb.getSheet(0).getRows()) { + Sheet sheet = wb.getSheetAt(0); + if (current_row > sheet.getLastRowNum()) { return null; } - String[] data = new String[wb.getSheet(0).getColumns()]; - for (int s = 0; s < wb.getSheet(0).getColumns(); s++) { - data[s] = wb.getSheet(0).getCell(s, current_row).getContents(); + Row row = sheet.getRow(current_row); + if (row == null) { + current_row++; + return new String[0]; + } + + int numCells = row.getLastCellNum(); + String[] data = new String[numCells]; + + for (int cellIndex = 0; cellIndex < numCells; cellIndex++) { + Cell cell = row.getCell(cellIndex, Row.RETURN_BLANK_AS_NULL); + data[cellIndex] = (cell == null) ? "" : formatter.formatCellValue(cell); } - current_row += 1; + + current_row++; return data; } @@ -375,6 +424,8 @@ public static class FieldFileXlsx extends FieldFileBase { private XSSFWorkbook wb; private int currentRow; + DataFormatter formatter = new DataFormatter(); + FieldFileXlsx(final Context ctx, final Uri path) { super(ctx, path); } @@ -428,29 +479,44 @@ public String[] getColumns() { return new String[0]; } - public HashMap getColumnSet(int idColPosition) { + @Override + public HashMap getColumnSet(String columnLabel, int idColPosition) { HashMap check = new HashMap<>(); + try { + open(); + XSSFSheet sheet = wb.getSheetAt(0); - XSSFSheet sheet = wb.getSheetAt(0); - - for (Iterator it = sheet.rowIterator(); it.hasNext(); ) { - XSSFRow row = (XSSFRow) it.next(); + for (Iterator it = sheet.rowIterator(); it.hasNext(); ) { + XSSFRow row = (XSSFRow) it.next(); + String value = getCellStringValue(row.getCell(idColPosition)); - String value = getCellStringValue(row.getCell(idColPosition)); + if (value.isEmpty() && isRowEmpty(row)) { + continue; // Skip the row if the specific cell is empty and the whole row is empty + } - if (check.containsKey(value)) { - return new HashMap<>(); - } else { - check.put(value, value); + if (!checkUnique(check, value, columnLabel, row.getRowNum())) { + return null; + } } + } catch (Exception e) { + setLastError("Failed to process XLSX file: " + e.getMessage()); + return null; + } finally { + close(); + } + return check; + } - if (value.contains("/") || value.contains("\\")) { - specialCharactersFail = true; + private boolean isRowEmpty(XSSFRow row) { + if (row == null || row.getLastCellNum() <= 0) { + return true; + } + for (Cell cell : row) { + if (cell != null && cell.getCellType() != Cell.CELL_TYPE_BLANK) { + return false; } - } - - return check; + return true; } public void open() { @@ -458,44 +524,28 @@ public void open() { } public String[] readNext() { - - DataFormatter fmt = new DataFormatter(); - ArrayList rows = new ArrayList<>(); XSSFSheet sheet = wb.getSheetAt(0); - - XSSFFormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator(); - - for (Iterator it = sheet.rowIterator(); it.hasNext();) { - rows.add((XSSFRow) it.next()); + if (currentRow > sheet.getLastRowNum()) { + return null; } - if (currentRow >= rows.size()) { - return null; + XSSFRow row = sheet.getRow(currentRow); + if (row == null) { + currentRow++; + return new String[0]; } - ArrayList data = new ArrayList<>(); - for (Iterator it = rows.get(currentRow).cellIterator(); it.hasNext();) { - XSSFCell cell = (XSSFCell) it.next(); - if (cell.getCellType() == Cell.CELL_TYPE_FORMULA) {//formula - int type = evaluator.evaluateFormulaCell(cell); - switch (type) { - case CELL_TYPE_BOOLEAN: - data.add(String.valueOf(cell.getBooleanCellValue())); - break; - case CELL_TYPE_NUMERIC: - data.add(String.valueOf(cell.getNumericCellValue())); - break; - default: - data.add(cell.getStringCellValue()); - break; - } - } else { - data.add(fmt.formatCellValue(cell)); - } + + int numCells = row.getLastCellNum(); + String[] data = new String[numCells]; + + for (int cellIndex = 0; cellIndex < numCells; cellIndex++) { + XSSFCell cell = row.getCell(cellIndex, Row.RETURN_BLANK_AS_NULL); + data[cellIndex] = (cell == null) ? "" : formatter.formatCellValue(cell); } - currentRow += 1; - return data.toArray(new String[] {}); + currentRow++; + return data; } public void close() { @@ -513,38 +563,20 @@ public void close() { } /** - * Helper function that reads the cell value and parses to string from xlsx sheets. - * @param cell the xssf cell object - * @return attempt to parse the string value of the cell + * Helper function that reads the cell value and formats it as a string, handling different cell types including formulas. + * @param cell the XSSFCell object from an Apache POI XSSFWorkbook. + * @return the formatted string value of the cell. */ private static String getCellStringValue(XSSFCell cell) { - if (cell == null) return ""; - FormulaEvaluator evaluator = cell.getSheet().getWorkbook().getCreationHelper().createFormulaEvaluator(); - - switch (cell.getCellType()) { - case 0: { //numeric - return String.valueOf(cell.getNumericCellValue()); - } - case 1: { //text - return cell.getStringCellValue(); - } - case Cell.CELL_TYPE_FORMULA: { //formula - switch (evaluator.evaluateFormulaCell(cell)) { - case CELL_TYPE_BOOLEAN: - return String.valueOf(cell.getBooleanCellValue()); - case CELL_TYPE_NUMERIC: - return String.valueOf(cell.getNumericCellValue()); - case CELL_TYPE_STRING: - return cell.getStringCellValue(); - } - } - case 3: { //boolean - return String.valueOf(cell.getBooleanCellValue()); - } - default: - return ""; + DataFormatter formatter = new DataFormatter(); + try { + // Use the DataFormatter to handle different data types uniformly + return formatter.formatCellValue(cell, cell.getSheet().getWorkbook().getCreationHelper().createFormulaEvaluator()); + } catch (Exception e) { + Log.e("FieldFileBase", "Error parsing cell value: " + e.getMessage()); + return ""; } } @@ -569,7 +601,7 @@ public String[] getColumns() { return new String[0]; } - public HashMap getColumnSet(int idColPosition) { + public HashMap getColumnSet(String columnLabel, int idColPosition) { return new HashMap<>(); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b17476194..04742d259 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -119,6 +119,13 @@ Database failed to switch fields. Failed to create field data for line %d in file. Duplicate columns found, only first instance will be used. + + Unique identifier value "%s" on row %d contains special characters that are not allowed. + Unique identifier value "%s" is duplicated on row %d. + Missing value found for unique identifier "%s" on row %d. + Missing value found for primary identifier "%s" on row %d. + Missing value found for secondary identifier "%s" on row %d. + This field already exists. This field is already imported at this observation level. Field Book could not load the file. From 3f29fd996873e3286e4c27ac9252256ac582bb97 Mon Sep 17 00:00:00 2001 From: bellerbrock Date: Tue, 10 Dec 2024 13:00:48 -0500 Subject: [PATCH 2/3] fix line number --- .../tracker/async/ImportRunnableTask.java | 6 +-- .../tracker/objects/FieldFileObject.java | 53 +++++++------------ 2 files changed, 23 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/com/fieldbook/tracker/async/ImportRunnableTask.java b/app/src/main/java/com/fieldbook/tracker/async/ImportRunnableTask.java index c437f5a6d..7685ec9f0 100644 --- a/app/src/main/java/com/fieldbook/tracker/async/ImportRunnableTask.java +++ b/app/src/main/java/com/fieldbook/tracker/async/ImportRunnableTask.java @@ -172,11 +172,11 @@ protected Integer doInBackground(Integer... params) { } else { fail = true; if (data[uniqueIndex].isEmpty()) { - failMessage = mContext.get().getString(R.string.import_runnable_create_field_missing_unique, unique, line); + failMessage = mContext.get().getString(R.string.import_runnable_create_field_missing_unique, unique, line+1); } else if (data[primaryIndex].isEmpty()) { - failMessage = mContext.get().getString(R.string.import_runnable_create_field_missing_primary, primary, line); + failMessage = mContext.get().getString(R.string.import_runnable_create_field_missing_primary, primary, line+1); } else if (data[secondaryIndex].isEmpty()) { - failMessage = mContext.get().getString(R.string.import_runnable_create_field_missing_secondary, secondary, line); + failMessage = mContext.get().getString(R.string.import_runnable_create_field_missing_secondary, secondary, line+1); } } } diff --git a/app/src/main/java/com/fieldbook/tracker/objects/FieldFileObject.java b/app/src/main/java/com/fieldbook/tracker/objects/FieldFileObject.java index 4ea2d5161..9b6ce5f66 100644 --- a/app/src/main/java/com/fieldbook/tracker/objects/FieldFileObject.java +++ b/app/src/main/java/com/fieldbook/tracker/objects/FieldFileObject.java @@ -4,6 +4,9 @@ import android.database.Cursor; import android.net.Uri; import android.provider.OpenableColumns; +import android.os.Build; +import android.text.Html; +import android.text.Spanned; import android.util.Log; import androidx.annotation.Nullable; @@ -106,7 +109,19 @@ protected boolean checkUnique(HashMap check, String value, Strin if (value.contains("/") || value.contains("\\")) { specialCharactersFail = true; - setLastError(ctx.getString(R.string.import_runnable_create_field_special_characters, value, rowIndex + 1)); + String rawMessage = ctx.getString( + R.string.import_runnable_create_field_special_characters, + value, + rowIndex + 1 + ); + Spanned formattedMessage; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + formattedMessage = Html.fromHtml(rawMessage, Html.FROM_HTML_MODE_LEGACY); + } else { + formattedMessage = Html.fromHtml(rawMessage); + } + + setLastError(formattedMessage.toString()); return false; } @@ -550,43 +565,15 @@ public void open() { } public String[] readNext() { - - DataFormatter fmt = new DataFormatter(); XSSFSheet sheet = wb.getSheetAt(0); - XSSFFormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator(); - - if (currentRow >= sheet.getPhysicalNumberOfRows()) { + if (currentRow > sheet.getLastRowNum()) { return null; } XSSFRow row = sheet.getRow(currentRow); - ArrayList data = new ArrayList<>(); - - int maxColumns = sheet.getRow(0).getLastCellNum(); // Get total number of columns from header - - for (int colIdx = 0; colIdx < maxColumns; colIdx++) { - XSSFCell cell = (row == null) ? null : row.getCell(colIdx); - - if (cell != null) { - if (cell.getCellType() == Cell.CELL_TYPE_FORMULA) {//formula - int type = evaluator.evaluateFormulaCell(cell); - switch (type) { - case CELL_TYPE_BOOLEAN: - data.add(String.valueOf(cell.getBooleanCellValue())); - break; - case CELL_TYPE_NUMERIC: - data.add(String.valueOf(cell.getNumericCellValue())); - break; - default: - data.add(cell.getStringCellValue()); - break; - } - } else { - data.add(fmt.formatCellValue(cell)); - } - } else { - data.add(""); // Add empty string for missing/empty cells - } + if (row == null) { + currentRow++; + return new String[0]; } From 4e34fc1c8331455641db74a4994d7cd1f6724f63 Mon Sep 17 00:00:00 2001 From: bellerbrock Date: Tue, 10 Dec 2024 17:48:56 -0500 Subject: [PATCH 3/3] bold key value in error report and include fix message --- .../tracker/async/ImportRunnableTask.java | 34 +++++++++---- .../tracker/objects/FieldFileObject.java | 48 +++++++++---------- app/src/main/res/values/strings.xml | 9 ++-- 3 files changed, 53 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/com/fieldbook/tracker/async/ImportRunnableTask.java b/app/src/main/java/com/fieldbook/tracker/async/ImportRunnableTask.java index 7685ec9f0..814b78ab2 100644 --- a/app/src/main/java/com/fieldbook/tracker/async/ImportRunnableTask.java +++ b/app/src/main/java/com/fieldbook/tracker/async/ImportRunnableTask.java @@ -18,6 +18,7 @@ import com.fieldbook.tracker.objects.FieldFileObject; import com.fieldbook.tracker.objects.FieldObject; import com.fieldbook.tracker.preferences.GeneralKeys; +import com.fieldbook.tracker.utilities.StringUtil; import com.fieldbook.tracker.utilities.Utils; import java.lang.ref.WeakReference; @@ -36,7 +37,7 @@ public class ImportRunnableTask extends AsyncTask { int lineFail = -1; boolean fail; - String failMessage; + private CharSequence failMessage; boolean uniqueFail; boolean containsDuplicates = false; private static final String TAG = "ImportRunnableTask"; @@ -171,12 +172,31 @@ protected Integer doInBackground(Integer... params) { } else { fail = true; + + String fixFileMessage = mContext.get().getString(R.string.import_runnable_create_field_fix_file); + String missingIdMessageTemplate = mContext.get().getString(R.string.import_runnable_create_field_missing_identifier); + + String missingField = null; + String fieldValue = null; + if (data[uniqueIndex].isEmpty()) { - failMessage = mContext.get().getString(R.string.import_runnable_create_field_missing_unique, unique, line+1); + missingField = mContext.get().getString(R.string.import_dialog_unique).toLowerCase(); + fieldValue = unique; } else if (data[primaryIndex].isEmpty()) { - failMessage = mContext.get().getString(R.string.import_runnable_create_field_missing_primary, primary, line+1); + missingField = mContext.get().getString(R.string.import_dialog_primary).toLowerCase(); + fieldValue = primary; } else if (data[secondaryIndex].isEmpty()) { - failMessage = mContext.get().getString(R.string.import_runnable_create_field_missing_secondary, secondary, line+1); + missingField = mContext.get().getString(R.string.import_dialog_secondary).toLowerCase(); + fieldValue = secondary; + } + + if (missingField != null) { + String missingIdMessage = String.format(missingIdMessageTemplate, missingField, fieldValue, line + 1); + failMessage = StringUtil.INSTANCE.applyBoldStyleToString( + String.format("%s\n\n%s", missingIdMessage, fixFileMessage), + fieldValue, + String.valueOf(line + 1) + ); } } } @@ -235,7 +255,7 @@ protected void onPostExecute(Integer result) { // Display user feedback in an alert dialog if (context != null && (uniqueFail || mFieldFile.hasSpecialCharacters())) { - String errorMessage = mFieldFile.getLastError(); + CharSequence errorMessage = mFieldFile.getLastError(); showAlertDialog(context, "Unable to Import", errorMessage); } else if (context != null && fail ) { showAlertDialog(context, "Unable to Import", failMessage); @@ -253,9 +273,7 @@ protected void onPostExecute(Integer result) { Log.d(TAG, "onPostExecute: Import successful. Field setup for ID: " + result); SharedPreferences.Editor ed = preferences.edit(); - CollectActivity.reloadData = true; - controller.queryAndLoadFields(); try { @@ -289,7 +307,7 @@ private boolean verifyUniqueColumn(FieldFileObject.FieldFileBase fieldFile) { } } - private void showAlertDialog(Context context, String title, String message) { + private void showAlertDialog(Context context, String title, CharSequence message) { new AlertDialog.Builder(context, R.style.AppAlertDialog) .setTitle(title) .setMessage(message) diff --git a/app/src/main/java/com/fieldbook/tracker/objects/FieldFileObject.java b/app/src/main/java/com/fieldbook/tracker/objects/FieldFileObject.java index 9b6ce5f66..60d9edfc2 100644 --- a/app/src/main/java/com/fieldbook/tracker/objects/FieldFileObject.java +++ b/app/src/main/java/com/fieldbook/tracker/objects/FieldFileObject.java @@ -72,7 +72,7 @@ public static String getExtensionFromClass(FieldFileBase fieldFile) { public abstract static class FieldFileBase { boolean openFail; boolean specialCharactersFail; - private String lastErrorMessage = ""; + private CharSequence lastErrorMessage = ""; private final Uri path_; private final Context ctx; @@ -85,44 +85,42 @@ public abstract static class FieldFileBase { specialCharactersFail = false; } - protected void setLastError(String message) { + protected void setLastError(CharSequence message) { this.lastErrorMessage = message; - Log.e("FieldFileBase", message); + Log.e("FieldFileBase", message.toString()); } - public String getLastError() { + public CharSequence getLastError() { return this.lastErrorMessage; } // Helper method to check unique values protected boolean checkUnique(HashMap check, String value, String columnLabel, int rowIndex) { - - if (value == null || value.isEmpty()) { - setLastError(ctx.getString(R.string.import_runnable_create_field_missing_unique, columnLabel, rowIndex + 1)); - return false; - } + String fixFileMessage = ctx.getString(R.string.import_runnable_create_field_fix_file); if (check.containsKey(value)) { - setLastError(ctx.getString(R.string.import_runnable_create_field_duplicate_unique, value, rowIndex + 1)); + String duplicateErrorMessage = ctx.getString( + R.string.import_runnable_create_field_duplicate_unique_identifier, value, columnLabel, + 1 + ); + setLastError(StringUtil.INSTANCE.applyBoldStyleToString( + String.format("%s\n\n%s", duplicateErrorMessage, fixFileMessage), + value, columnLabel + )); return false; } - if (value.contains("/") || value.contains("\\")) { - specialCharactersFail = true; - String rawMessage = ctx.getString( - R.string.import_runnable_create_field_special_characters, - value, - rowIndex + 1 - ); - Spanned formattedMessage; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - formattedMessage = Html.fromHtml(rawMessage, Html.FROM_HTML_MODE_LEGACY); - } else { - formattedMessage = Html.fromHtml(rawMessage); + for (char specialChar : new char[]{'/', '\\'}) { + if (value.contains(String.valueOf(specialChar))) { + String specialCharErrorMessage = ctx.getString( + R.string.import_runnable_create_field_special_character_error, value, columnLabel, rowIndex + 1, specialChar + ); + setLastError(StringUtil.INSTANCE.applyBoldStyleToString( + String.format("%s\n\n%s", specialCharErrorMessage, fixFileMessage), + value, columnLabel + )); + specialCharactersFail = true; + return false; } - - setLastError(formattedMessage.toString()); - return false; } check.put(value, value); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f3a4a567f..921f3b329 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -160,11 +160,10 @@ Failed to create field data for line %d in file. Duplicate columns found, only first instance will be used. - Unique identifier value "%s" on row %d contains special characters that are not allowed. - Unique identifier value "%s" is duplicated on row %d. - Missing value found for unique identifier "%s" on row %d. - Missing value found for primary identifier "%s" on row %d. - Missing value found for secondary identifier "%s" on row %d. + Unique identifier value %s in column %s is duplicated on row %d. + Unique identifier value %s in column %s on row %d contains special character "%s" which is not allowed. + Missing value for %s %s on row %d. + Please correct the file and try importing it again. This field already exists. This field is already imported at this observation level.