Replacement for "GROUP BY" in ContentResolver query in Android Q ( Android 10, API 29 changes) - android-sqlite

I'm upgrading some legacy to target Android Q, and of course this code stop working:
String[] PROJECTION_BUCKET = {MediaStore.Images.ImageColumns.BUCKET_ID,
MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,
MediaStore.Images.ImageColumns.DATE_TAKEN,
MediaStore.Images.ImageColumns.DATA,
"COUNT(" + MediaStore.Images.ImageColumns._ID + ") AS COUNT",
MediaStore.Files.FileColumns.MEDIA_TYPE,
MediaStore.MediaColumns._ID};
String BUCKET_GROUP_BY = " 1) and " + BUCKET_WHERE.toString() + " GROUP BY 1,(2";
cur = context.getContentResolver().query(images, PROJECTION_BUCKET,
BUCKET_GROUP_BY, null, BUCKET_ORDER_BY);
android.database.sqlite.SQLiteException: near "GROUP": syntax error (code 1 SQLITE_ERROR[1])
Here it supposed to obtain list of images with album name, date, count of pictures - one image for each album, so we can create album picker screen without querying all pictures and loop through it to create albums.
Is it possible to group query results with contentResolver since SQL queries stoped work?
(I know that ImageColumns.DATA and "COUNT() AS COUNT" are deprecated too, but this is a question about GROUP BY)
(There is a way to query albums and separately query photo, to obtain photo uri for album cover, but i want to avoid overheads)

Unfortunately Group By is no longer supported in Android 10 and above, neither any aggregated functions such as COUNT. This is by design and there is no workaround.
The solution is what you are actually trying to avoid, which is to query, iterate, and get metrics.
To get you started you can use the next snipped, which will resolve the buckets (albums), and the amount of records in each one.
I haven't added code to resolve the thumbnails, but is easy. You must perform a query for each bucket Id from all the Album instances, and use the image from the first record.
public final class AlbumQuery
{
#NonNull
public static HashMap<String, AlbumQuery.Album> get(#NonNull final Context context)
{
final HashMap<String, AlbumQuery.Album> output = new HashMap<>();
final Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
final String[] projection = {MediaStore.Images.Media.BUCKET_DISPLAY_NAME, MediaStore.Images.Media.BUCKET_ID};
try (final Cursor cursor = context.getContentResolver().query(contentUri, projection, null, null, null))
{
if ((cursor != null) && (cursor.moveToFirst() == true))
{
final int columnBucketName = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_DISPLAY_NAME);
final int columnBucketId = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_ID);
do
{
final String bucketId = cursor.getString(columnBucketId);
final String bucketName = cursor.getString(columnBucketName);
if (output.containsKey(bucketId) == false)
{
final int count = AlbumQuery.getCount(context, contentUri, bucketId);
final AlbumQuery.Album album = new AlbumQuery.Album(bucketId, bucketName, count);
output.put(bucketId, album);
}
} while (cursor.moveToNext());
}
}
return output;
}
private static int getCount(#NonNull final Context context, #NonNull final Uri contentUri, #NonNull final String bucketId)
{
try (final Cursor cursor = context.getContentResolver().query(contentUri,
null, MediaStore.Images.Media.BUCKET_ID + "=?", new String[]{bucketId}, null))
{
return ((cursor == null) || (cursor.moveToFirst() == false)) ? 0 : cursor.getCount();
}
}
public static final class Album
{
#NonNull
public final String buckedId;
#NonNull
public final String bucketName;
public final int count;
Album(#NonNull final String bucketId, #NonNull final String bucketName, final int count)
{
this.buckedId = bucketId;
this.bucketName = bucketName;
this.count = count;
}
}
}

This is a more efficient(not perfect) way to do that.
I am doing it for videos, but doing so is the same for images to. just change MediaStore.Video.Media.X to MediaStore.Images.Media.X
public class QUtils {
/*created by Nasib June 6, 2020*/
#RequiresApi(api = Build.VERSION_CODES.Q)
public static ArrayList<FolderHolder> loadListOfFolders(Context context) {
ArrayList<FolderHolder> allFolders = new ArrayList<>();//list that we need
HashMap<Long, String> folders = new HashMap<>(); //hashmap to track(no duplicates) folders by using their ids
String[] projection = {MediaStore.Video.Media._ID,
MediaStore.Video.Media.BUCKET_ID,
MediaStore.Video.Media.BUCKET_DISPLAY_NAME,
MediaStore.Video.Media.DATE_ADDED};
ContentResolver CR = context.getContentResolver();
Uri root = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
Cursor c = CR.query(root, projection, null, null, MediaStore.Video.Media.DATE_ADDED + " desc");
if (c != null && c.moveToFirst()) {
int folderIdIndex = c.getColumnIndexOrThrow(MediaStore.Video.Media.BUCKET_ID);
int folderNameIndex = c.getColumnIndexOrThrow(MediaStore.Video.Media.BUCKET_DISPLAY_NAME);
int thumbIdIndex = c.getColumnIndexOrThrow(MediaStore.Video.Media._ID);
int dateAddedIndex = c.getColumnIndexOrThrow(MediaStore.Video.Media.DATE_ADDED);
do {
Long folderId = c.getLong(folderIdIndex);
if (folders.containsKey(folderId) == false) { //proceed only if the folder data has not been inserted already :)
long thumbId = c.getLong(thumbIdIndex);
String folderName = c.getString(folderNameIndex);
String dateAdded = c.getString(dateAddedIndex);
Uri thumbPath = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, thumbId);
folders.put(folderId, folderName);
allFolders.add(new FolderHolder(String.valueOf(thumbPath), folderName, dateAdded));
}
} while (c.moveToNext());
c.close(); //close cursor
folders.clear(); //clear the hashmap becuase it's no more useful
}
return allFolders;
}
}
FolderHolder model class
public class FolderHolder {
private String folderName;
public long dateAdded;
private String thumbnailPath;
public long folderId;
public void setPath(String thumbnailPath) {
this.thumbnailPath = thumbnailPath;
}
public String getthumbnailPath() {
return thumbnailPath;
}
public FolderHolder(long folderId, String thumbnailPath, String folderName, long dateAdded) {
this.folderId = folderId;
this.folderName = folderName;
this.thumbnailPath = thumbnailPath;
this.dateAdded = dateAdded;
}
public String getFolderName() {
return folderName;
}
}

GROUP_BY supporting in case of using Bundle:
val bundle = Bundle().apply {
putString(
ContentResolver.QUERY_ARG_SQL_SORT_ORDER,
"${MediaStore.MediaColumns.DATE_MODIFIED} DESC"
)
putString(
ContentResolver.QUERY_ARG_SQL_GROUP_BY,
MediaStore.Images.ImageColumns.BUCKET_ID
)
}
contentResolver.query(
uri,
arrayOf(
MediaStore.Images.ImageColumns.BUCKET_ID,
MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,
MediaStore.Images.ImageColumns.DATE_TAKEN,
MediaStore.Images.ImageColumns.DATA
),
bundle,
null
)

Related

Database update without data loss. FATAL EXCEPTION: ModernAsyncTask #1

I need to implement an update of the database lying in the assets. User data, namely, in the "favorite" record or not, should be saved.
I already asked a question and they helped me -https://stackoverflow.com/a/53827525/10261947
Everything worked in a test application. But when I transferred the code (exactly the same) to the real application, an error occurs - E/AndroidRuntime: FATAL EXCEPTION: ModernAsyncTask #1
Process: rodionova.lyubov.brodsky, PID: 4196
java.lang.RuntimeException: An error occurred while executing doInBackground()
at android.support.v4.content.ModernAsyncTask$3.done(ModernAsyncTask.java:161)
at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383)
at java.util.concurrent.FutureTask.setException(FutureTask.java:252)
at java.util.concurrent.FutureTask.run(FutureTask.java:271)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:784)
Caused by: java.lang.IllegalArgumentException: the bind value at index 4 is null
at android.database.sqlite.SQLiteProgram.bindString(SQLiteProgram.java:169)
at android.database.sqlite.SQLiteProgram.bindAllArgsAsStrings(SQLiteProgram.java:205)
at android.database.sqlite.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:47)
at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1397)
at android.database.sqlite.SQLiteDatabase.queryWithFactory(SQLiteDatabase.java:1239)
at android.database.sqlite.SQLiteDatabase.query(SQLiteDatabase.java:1110)
at android.database.sqlite.SQLiteDatabase.query(SQLiteDatabase.java:1278)
at rodionova.lyubov.brodsky.db.PoemsDbHelper.insertCorePoem(PoemsDbHelper.java:121)
at rodionova.lyubov.brodsky.db.PoemsDbHelper.getNewPoems(PoemsDbHelper.java:90)
at rodionova.lyubov.brodsky.db.PoemsDbHelper.onUpgrade(PoemsDbHelper.java:41)
at com.readystatesoftware.sqliteasset.SQLiteAssetHelper.getWritableDatabase(SQLiteAssetHelper.java:197)
at com.readystatesoftware.sqliteasset.SQLiteAssetHelper.getReadableDatabase(SQLiteAssetHelper.java:254)
at rodionova.lyubov.brodsky.db.PoemsProvider.query(PoemsProvider.java:45)
at android.content.ContentProvider.query(ContentProvider.java:1057)
If you do not perform the update, the application is working properly, so I will post only the code DbHelper
public class PoemsDbHelper extends SQLiteAssetHelper {
public static final String DB_NAME = "brodsky.db";
public static final int DBVERSION = 3;
public static final String TBLNAME = "poems_table";
public static final String COL_ID = "id";
public static final String COL_TITLE = "title";
public static final String COl_POEM = "poem";
public static final String COL_SUBJECT = "subject";
public static final String COL_YEARS = "years";
public static final String COL_FAVOURITE = "favorite";
Context mContext;
public PoemsDbHelper(Context context) {
super(context, DB_NAME, null, DBVERSION);
mContext = context;
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if(newVersion > oldVersion)
getNewPoems(mContext, db);
}
private void getNewPoems(Context context, SQLiteDatabase db) {
InputStream is;
OutputStream os;
final String tempNewDbName = "temp_brodsky.db";
int buffersize = 4096;
byte[] buffer = new byte[buffersize];
String newDBPath = mContext.getDatabasePath(tempNewDbName).getPath();
File newDBFile = new File(newDBPath);
if (newDBFile.exists()) {
newDBFile.delete();
}
File newDBFileDirectory = newDBFile.getParentFile();
if (!newDBFileDirectory.exists()) {
newDBFileDirectory.mkdirs();
}
try {
is = context.getAssets().open("databases/" + DB_NAME);
os = new FileOutputStream(newDBFile);
int bytes_read;
while ((bytes_read = is.read(buffer,0,buffersize)) > 0) {
os.write(buffer);
}
os.flush();
os.close();
is.close();
}catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("Ouch updated database not copied - processing stopped - see stack-trace above.");
}
long id = maxid(db) + 1;
SQLiteDatabase newdb = SQLiteDatabase.openDatabase(newDBFile.getPath(),null,SQLiteDatabase.OPEN_READONLY);
Cursor csr = newdb.query(TBLNAME,null,null,null,null,null,null);
long insert_result;
db.beginTransaction();
while (csr.moveToNext()) {
insert_result = insertCorePoem(
db,
id,
csr.getString(csr.getColumnIndex(COL_TITLE)),
csr.getString(csr.getColumnIndex(COl_POEM)),
csr.getString(csr.getColumnIndex(COL_SUBJECT)),
csr.getString(csr.getColumnIndex(COL_YEARS)),
csr.getString(csr.getColumnIndex(COL_FAVOURITE))
);
if (insert_result > 0) {
id++;
}
}
db.setTransactionSuccessful();
db.endTransaction();
csr.close();
newDBFile.delete();
}
public long insertCorePoem(SQLiteDatabase db, long id, String title, String poem, String subject, String years, String favourite) {
String whereclause = COL_TITLE + "=? AND " + COl_POEM + "=? AND " + COL_SUBJECT + "=? AND " + COL_YEARS + "=?";
String[] whereargs = new String[]{
title,
poem,
subject,
years
};
Cursor csr = db.query(TBLNAME,null,whereclause,whereargs,null,null,null);
boolean rowexists = (csr.getCount() > 0);
csr.close();
if (rowexists) {
Log.d("INSERTCOREPOEM","Skipping insert of row");
return -2;
}
ContentValues cv = new ContentValues();
cv.put(COL_ID,id);
cv.put(COL_TITLE,title);
cv.put(COl_POEM,poem);
cv.put(COL_SUBJECT,subject);
cv.put(COL_YEARS,years);
cv.put(COL_FAVOURITE,favourite);
Log.d("INSERTCOREPOEM","Inserting new column with id " + String.valueOf(id));
return db.insert(TBLNAME, null, cv);
}
private long maxid(SQLiteDatabase db) {
long rv = 0;
String extractcolumn = "maxid";
String[] col = new String[]{"max(" + COL_ID + ") AS " + extractcolumn};
Cursor csr = db.query(TBLNAME,col,null,null,null,null,null);
if (csr.moveToFirst()) {
rv = csr.getLong(csr.getColumnIndex(extractcolumn));
}
csr.close();
return rv;
}
}
I do not understand what is wrong. Identical code works great friend application. I would be grateful for the help.
Your issue is that you likely have a value of null in the years column of a row or rows in the updated database that data is being copied from.
Although you could change the code to handle (skip insertion or use provide a year value) the end result may not be desired. So the most likely fix would be to amend the database to have valid/useful year values.

Deserializing Dates from mongodb with custom CodecProvider in Java gives null results

I have implemented a custom MongoDB CodecProvider to map to my java objects, using this Github gist. However, i cannot deserialize Date values, rather null values are returned. Here is the snippet of my custom encoder implementation for my pojo - AuditLog:
public void encode(BsonWriter writer, AuditLog value, EncoderContext encoderContext) {
Document document = new Document();
DateCodec dateCodec = new DateCodec();
ObjectId id = value.getLogId();
Date timestamp = value.getTimestamp();
String deviceId = value.getDeviceId();
String userId = value.getUserId();
String requestId = value.getRequestId();
String operationType = value.getOperationType();
String message = value.getMessage();
String serviceName = value.getServiceName();
String className = value.getClassName();
if (null != id) {
document.put("_id", id);
}
if (null != timestamp) {
document.put("timestamp", timestamp);
}
if (null != deviceId) {
document.put("deviceId", deviceId);
}
if (null != userId) {
document.put("userId", userId);
}
if (null != requestId) {
document.put("requestId", requestId);
}
if (null != operationType) {
document.put("operationType", operationType);
}
if (null != message) {
document.put("message", message);
}
if (null != serviceName) {
document.put("serviceName", serviceName);
}
if (null != className) {
document.put("className", className);
}
documentCodec.encode(writer, document, encoderContext);
}
and decoder:
public AuditLog decode(BsonReader reader, DecoderContext decoderContext) {
Document document = documentCodec.decode(reader, decoderContext);
System.out.println("document " + document);
AuditLog auditLog = new AuditLog();
auditLog.setLogId(document.getObjectId("_id"));
auditLog.setTimestamp(document.getDate("timestamp"));
auditLog.setDeviceId(document.getString("deviceId"));
auditLog.setUserId(document.getString("userId"));
auditLog.setRequestId(document.getString("requestId"));
auditLog.setOperationType(document.getString("operationType"));
auditLog.setMessage(document.getString("message"));
auditLog.setServiceName(document.getString("serviceName"));
auditLog.setClassName(document.getString("className"));
return auditLog;
}
and the way I an reading:
public void getAuthenticationEntries() {
Codec<Document> defaultDocumentCodec = MongoClient.getDefaultCodecRegistry().get(Document.class);
AuditLogCodec auditLogCodec = new AuditLogCodec(defaultDocumentCodec);
CodecRegistry codecRegistry = CodecRegistries.fromRegistries(MongoClient.getDefaultCodecRegistry(),
CodecRegistries.fromCodecs(auditLogCodec));
MongoClientOptions options = MongoClientOptions.builder().codecRegistry(codecRegistry).build();
MongoClient mc = new MongoClient("1.2.3.4:27017", options);
MongoCollection<AuditLog> collection = mc.getDatabase("myDB").getCollection("myCol",
AuditLog.class);
BasicDBObject neQuery = new BasicDBObject();
neQuery.put("myFiltr", new BasicDBObject("$eq", "mystuffr"));
FindIterable<AuditLog> cursor = collection.find(neQuery);
List<AuditLog> cleanList = new ArrayList<AuditLog>();
for (AuditLog object : cursor) {
System.out.println("timestamp: " + object.getTimestamp());
}
}
My pojo:
public class AuditLog implements Bson {
#Id
private ObjectId logId;
#JsonProperty("#timestamp")
private Date timestamp;
#JsonProperty("deviceId")
private String deviceId;
#JsonProperty("userId")
private String userId;
#JsonProperty("requestId")
private String requestId;
#JsonProperty("operationType")
private String operationType;
#JsonProperty("message")
private String message;
#JsonProperty("serviceName")
private String serviceName;
#JsonProperty("className")
private String className;
After a thorough research, I fixed the problem of returned null values. The mongoimport command was used to import the log files into Mongodbfrom elasticsearch. However, the time format was not converted to ISODate during the import operation. What I had to do was to update the time format to ISODate using the below command:
db.Collection.find().forEach(function (doc){
doc.time = Date(time);
});
db.dummy.save(doc);
Here is a related question that tackles a similar challenge.

QuickfixJ create message from xml string

QuickFixJ Message class has method toXML() which converts message into xml string.
Is there any way I can create message object from the XML string?
I need the reverse of toXML() i.e. I want to create Message from xml.
There's nothing like that built in. There doesn't really need to be, as there normally wouldn't be a use-case for it.
I've written a class which does it. The order of the tags might be different from the input message (but the FIX spec makes no guarantees about tag order, except within groups) because the XML exporter sorts by tag number, and so the original tag order is lost.
It only works on a single message in an XML file, but could easily be adapted to work on multiple messages.
You can use the standard MessageUtils.parse to create a Message from the resultant string.
Let me know if you have any problems.
class XmlMessage
{
private final String xml;
private final String delimiter;
XmlMessage(final String xml, final String delimiter)
{
this.xml = xml;
this.delimiter = delimiter;
}
public String toFixMessage() throws IOException, SAXException, ParserConfigurationException
{
final Document doc = DocumentBuilderFactory.newInstance()
.newDocumentBuilder()
.parse(new ByteArrayInputStream(xml.getBytes()));
final StringBuilder messageBuilder = new StringBuilder();
build(messageBuilder, doc, "header");
build(messageBuilder, doc, "body");
build(messageBuilder, doc, "trailer");
return messageBuilder.toString();
}
private void build(final StringBuilder messageBuilder, final Document doc, final String section)
{
final NodeList sectionRoot = doc.getElementsByTagName(section);
final NodeList sectionChildren = sectionRoot.item(0).getChildNodes();
build(messageBuilder, sectionChildren);
}
private void build(final StringBuilder messageBuilder, final NodeList nodeList)
{
final Set<String> numInGroupTags = getNumInGroupTags(nodeList);
for (int i = 0; i < nodeList.getLength(); i++)
{
final Node node = nodeList.item(i);
if (node.getNodeName().equals("field") && !numInGroupTags.contains(getTagNumber(node)))
{
messageBuilder.append(getTagNumber(node))
.append('=')
.append(node.getTextContent())
.append(delimiter);
}
else if (node.getNodeName().equals("groups"))
{
final NodeList groupElems = node.getChildNodes();
messageBuilder.append(getTagNumber(node))
.append('=')
.append(getGroupCount(groupElems))
.append(delimiter);
for (int j = 0; j < groupElems.getLength(); j++)
{
build(messageBuilder, groupElems.item(j).getChildNodes());
}
}
}
}
private Set<String> getNumInGroupTags(final NodeList nodeList)
{
final Set<String> numInGroupTags = new HashSet<>();
for (int i = 0; i < nodeList.getLength(); i++)
{
if (nodeList.item(i).getNodeName().equals("groups"))
{
numInGroupTags.add(getTagNumber(nodeList.item(i)));
}
}
return numInGroupTags;
}
private String getTagNumber(final Node node)
{
return node.getAttributes().getNamedItem("tag").getTextContent();
}
private int getGroupCount(final NodeList groupRoot)
{
int count = 0;
for (int j = 0; j < groupRoot.getLength(); j++)
{
if (groupRoot.item(j).getNodeName().equals("group")) count++;
}
return count;
}
}

I need to show different tool tip for each table cell

Please see this
This is what I want to see... I mean the tool tip
At run time I get this error message for each row that is being loaded.
The tool tip text lies in the an object and is retrieved by thisRow.getCourseTootip(i);
Number of columns in the table varies and I create them and add them to the table view thru code.
for (int courseNo = 0; courseNo < numberOfCourses; courseNo++) {
String colName = getASemesterCourse(thisSemester, courseNo).getCourseID();
TableColumn<AResultRow, String> thisColumn = new TableColumn<>(colName);
thisColumn.setPrefWidth(80);
thisColumn.setStyle("-fx-alignment: CENTER; font-weight:bold;");
String str = TableRows.get(1).getGrade(courseNo);
final int i = courseNo;
thisColumn.setCellValueFactory(cellData -> cellData.getValue().courseGradeProperty(i));
thisColumn.setCellFactory(new Callback<TableColumn<AResultRow, String>, TableCell<AResultRow, String>>() {
public TableCell<AResultRow, String> call(TableColumn<AResultRow, String> column) {
return new TableCell<AResultRow, String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setText(item);
AResultRow thisRow = new AResultRow();
thisRow = getTableView().getItems().get(getTableRow().getIndex());
final Tooltip tip= new Tooltip();
tip.setText(thisRow.getCourseTootip(i));
setTooltip(tip);
tip.setStyle("-fx-background-color: pink; -fx-text-fill: black; -fx-font: normal normal 12pt \"Times New Roman\"");
}
}
};
}
});
boolean retVal = thisTable.getColumns().addAll(thisColumn);
}
Error is
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at victoriairene.TheMainFXMLController$1$1.updateItem(TheMainFXMLController.java:434)
at victoriairene.TheMainFXMLController$1$1.updateItem(TheMainFXMLController.java:427)
Line 434 is
thisRow = getTableView().getItems().get(getTableRow().getIndex());
Text for the tool tip for this cell comes from thisRow.getCourseTootip(i).
Can someone tell me, what is wrong with my code? Which object is null ? If it is null, then how do I get to see the correct Tooltip text, in spite of getting error messages for each row ?
I have been struggling with this for one full day.
Please help and thanks in advance.
As requested by Kleopatra I am enclosing the entire Create Table function.
public void createTableForThisSemester(int thisSemester, int numberOfCourses, javafx.collections.ObservableList<AResultRow> TableRows) {
TableView<AResultRow> thisTable = new TableView<>();
thisTable.setContextMenu(contextMenu);
TableColumn<AResultRow, String> tcolRollNo = new TableColumn<>("Roll Number");
tcolRollNo.setEditable(false);
tcolRollNo.setPrefWidth(120);
TableColumn<AResultRow, String> tcolName = new TableColumn<>("Student Name");
tcolName.setEditable(false);
tcolName.setPrefWidth(350);
tcolRollNo.setCellValueFactory(cellData -> cellData.getValue().StudentIDProperty());
tcolName.setCellValueFactory(cellData -> cellData.getValue().StudentNameProperty());
boolean xyz = thisTable.getColumns().addAll(tcolRollNo, tcolName);
// TableColumn[] courseColumn = new TableColumn[numberOfCourses];
for (int courseNo = 0; courseNo < numberOfCourses; courseNo++) {
String colName = getASemesterCourse(thisSemester, courseNo).getCourseID();
TableColumn<AResultRow, String> thisColumn = new TableColumn<>(colName);
thisColumn.setPrefWidth(80);
thisColumn.setStyle("-fx-alignment: CENTER; font-weight:bold;");
String str = TableRows.get(1).getGrade(courseNo);
final int i = courseNo;
thisColumn.setCellValueFactory(cellData -> cellData.getValue().courseGradeProperty(i));
thisColumn.setCellFactory(new Callback<TableColumn<AResultRow, String>, TableCell<AResultRow, String>>() {
public TableCell<AResultRow, String> call(TableColumn<AResultRow, String> column) {
return new TableCell<AResultRow, String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setText(item);
AResultRow thisRow = new AResultRow();
thisRow = getTableView().getItems().get(getTableRow().getIndex());
final Tooltip tip= new Tooltip();
tip.setText(thisRow.getCourseTootip(i));
setTooltip(tip);
tip.setStyle("-fx-background-color: pink; -fx-text-fill: black; -fx-font: normal normal 12pt \"Times New Roman\"");
}
}
};
}
});
boolean retVal = thisTable.getColumns().addAll(thisColumn);
}
// System.out.println("# of Rows in Table [" + thisSemester + "] = " + TableRows.size());
TableColumn<AResultRow, String> tcolGPA = new TableColumn<>("GPA");
tcolGPA.setEditable(false);
tcolGPA.setPrefWidth(80);
tcolGPA.setStyle("-fx-alignment: CENTER; font-weight:bold;");
tcolGPA.setCellValueFactory(cellData -> cellData.getValue().returnStringGPA());
boolean retVal = thisTable.getColumns().addAll(tcolGPA);
thisTable.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
thisTable.setItems(TableRows);
thisTable.getSelectionModel().selectedItemProperty().addListener((observableValue, oldValue, newValue) -> {
//Check whether item is selected and set value of selected item to Label
if (thisTable.getSelectionModel().getSelectedItem() == null) {
gRollNumber = null;
gStudentName = null;
} else {
gRollNumber = newValue.getStudentID();
gStudentName = newValue.getStudentName();
}
});
ScrollPane thisScrollPane = new ScrollPane();
thisScrollPane.setFitToWidth(true);
thisScrollPane.setFitToHeight(true);
thisScrollPane.setMinHeight((theDetails.getHeight() - 25));
thisScrollPane.setMaxHeight((theDetails.getHeight() - 25));
thisScrollPane.setMinWidth((theDetails.getWidth() - 25));
thisScrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
Tab thisTab = tabs.getTabs().get(thisSemester);
thisTab.setContent(thisScrollPane);
thisScrollPane.setContent(thisTable);
}
I am repeating the hierarchy again - please excuse.
Table view is associated with an observablelist named ATableRows which a class ATableRow.
ATableRow contains several members and one of them is an array of class ACourseResult.
I need to know the ROW number and the array index (which is actually the Table Column number for that Cell) before I can retrieve the text for the tooltip.
Thing is the code works... except for the runtime error of null pointer. I still do not understand what the CellFactory and CellValueFactories do. Sorry about that. Oracle's documents do not say what they do......
While I am at this.... I want to tell you that my TABLE is READ ONLY. Do I Have to use the Observable List ? Can't I do this by setting values directly to each cell (just a curiosity).
Thanks in advance and sorry if my questions seem dumber.
Thanks to JAMES my problem is solved.... I am enclosing the modified code for others.
for (int courseNo = 0; courseNo < numberOfCourses; courseNo++) {
String colName = getASemesterCourse(thisSemester, courseNo).getCourseID();
TableColumn<AResultRow, String> thisColumn = new TableColumn<>(colName);
thisColumn.setPrefWidth(80);
thisColumn.setStyle("-fx-alignment: CENTER; font-weight:bold;");
String str = TableRows.get(1).getGrade(courseNo);
final int i = courseNo;
thisColumn.setCellValueFactory(cellData -> cellData.getValue().courseGradeProperty(i));
thisColumn.setCellFactory(new Callback<TableColumn<AResultRow, String>, TableCell<AResultRow, String>>() {
public TableCell<AResultRow, String> call(TableColumn<AResultRow, String> column) {
return new TableCell<AResultRow, String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setText(item);
AResultRow thisRow = new AResultRow();
final int k = this.getIndex(); **// These are the changes suggested by James**
thisRow = getTableView().getItems().get(k); ***// These are the changes suggested by James***
// thisRow = getTableView().getItems().get(getTableRow().getIndex()); <- this is the old code commented out.
final Tooltip tip= new Tooltip();
tip.setText(thisRow.getCourseTootip(i));
setTooltip(tip);
tip.setStyle("-fx-background-color: pink; -fx-text-fill: black; -fx-font: normal normal 12pt \"Times New Roman\"");
}
}
};
}
});
boolean retVal = thisTable.getColumns().addAll(thisColumn);
}
The quick, and wrong, answer to the question is to point out that TableCell has a getTableRow() method, that will give you the row data without having to look it up through the underlying data via the row index.
Technically, you should give the TableCell all of the information it needs to work independently when its updateItem() method is called. This would mean restructing the data model such that the table cell is given a structure with both the displayed contents of the cell, and the text to go in the tooltip. It would appear that the AResultRow data structure has two associated lists, one with whatever shows in the cell, and the other with whatever goes into the tooltip. My suggestion would be to refactor that so that it's a single list holding objects which contain both data elements. Once you do that, the rest of the table structure becomes trivial. Here's a working example:
public class SampleTable extends Application {
private BorderPane testPane;
class TestPane extends BorderPane {
public TestPane(List<DataModel> dataItems) {
TableView<DataModel> tableView = new TableView<DataModel>();
setCenter(tableView);
TableColumn<DataModel, ClassInfo> column1 = new TableColumn<DataModel, ClassInfo>("column 1");
TableColumn<DataModel, ClassInfo> column2 = new TableColumn<DataModel, ClassInfo>("column 2");
column1.setCellValueFactory(new PropertyValueFactory<DataModel, ClassInfo>("column1Data"));
column2.setCellValueFactory(new PropertyValueFactory<DataModel, ClassInfo>("column2Data"));
column1.setCellFactory(column -> new CustomTableCell());
column2.setCellFactory(column -> new CustomTableCell());
tableView.getColumns().addAll(column1, column2);
tableView.setItems(FXCollections.observableList(dataItems));
}
}
public static void main(String[] args) {
launch(args);
}
public class CustomTableCell extends TableCell<DataModel, ClassInfo> {
#Override
protected void updateItem(ClassInfo item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
if (item != null) {
setText(item.getName());
setTooltip(new Tooltip(item.getDescription()));
}
}
}
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Task Progress Tester");
List<DataModel> dataItems = new ArrayList<DataModel>();
DataModel row1Data = new DataModel();
row1Data.setColumn1Data(new ClassInfo("row1Col1Name", "This is the description for Row1, Column 1"));
row1Data.setColumn2Data(new ClassInfo("row1Col2Name", "This is the description for Row1, Column 2"));
dataItems.add(row1Data);
DataModel row2Data = new DataModel();
row2Data.setColumn1Data(new ClassInfo("row2Col1Name", "This is the description for Row2, Column 1"));
row2Data.setColumn2Data(new ClassInfo("row2Col2Name", "This is the description for Row2, Column 2"));
dataItems.add(row2Data);
testPane = new TestPane(dataItems);
primaryStage.setScene(new Scene(testPane, 300, 250));
primaryStage.show();
}
public class DataModel {
private ObjectProperty<ClassInfo> column1Data = new SimpleObjectProperty<ClassInfo>();
private ObjectProperty<ClassInfo> column2Data = new SimpleObjectProperty<ClassInfo>();
public void setColumn2Data(ClassInfo newValue) {
column2Data.set(newValue);
}
public void setColumn1Data(ClassInfo newValue) {
column1Data.set(newValue);
}
public ObjectProperty<ClassInfo> column1DataProperty() {
return column1Data;
}
}
public class ClassInfo {
private String name;
private String description;
public ClassInfo(String name, String description) {
this.setName(name);
this.setDescription(description);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
}

SQLite app won't run at all. Force close message appears

I am creating an app using SQLite.
It crashes before loading.
I fixed all viable errors and since I do not even get a log info, I am having troubles figuring out the error.
Please help.
SQLHelper
public class MySQLiteHelper extends SQLiteOpenHelper {
// Database Version
private static final int DATABASE_VERSION = 1;
// Database Name
private static final String DATABASE_NAME = "MediaDB";
// Media table name
private static final String TABLE_MEDIA = "media";
// Media Table Columns names
private static final String KEY_ID = "id";
private static final String KEY_TYPE = "type";
private static final String KEY_TITLE = "title";
private static final String KEY_AUTHOR = "author";
private static final String[] COLUMNS = {KEY_ID,KEY_TYPE,KEY_TITLE,KEY_AUTHOR};
public MySQLiteHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
#Override
public void onCreate(SQLiteDatabase db) {
// SQL statement to create media table
String CREATE_MEDIA_TABLE = "CREATE TABLE media ( " +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"tyoe TYPE, "+
"title TEXT, "+
"author TEXT )";
// create media table
db.execSQL(CREATE_MEDIA_TABLE);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Drop older media table if existed
db.execSQL("DROP TABLE IF EXISTS media");
// create fresh media table
this.onCreate(db);
}
//ADD MEDIA
public void addMedia(Media media){
//for logging
Log.d("addMedia", media.toString());
// get reference to writable DB
SQLiteDatabase db = this.getWritableDatabase();
// create ContentValues to add key "column"/value
ContentValues values = new ContentValues();
values.put(KEY_TYPE, media.getType()); // get title
values.put(KEY_TITLE, media.getTitle()); // get title
values.put(KEY_AUTHOR, media.getAuthor()); // get author
// insert
db.insert(TABLE_MEDIA, // table
null, //nullColumnHack
values); // key/value -> keys = column names/ values = column values
// close
db.close();
}
//GET MEDIA
public Media getMedia(int id){
// get reference to readable DB
SQLiteDatabase db = this.getReadableDatabase();
// build query
Cursor cursor =
db.query(TABLE_MEDIA, // table
COLUMNS, // column names
" id = ?", // selections
new String[] { String.valueOf(id) }, // d. selections args
null, // group by
null, // having
null, // order by
null); // limit
// if we got results get the first one
if (cursor != null)
cursor.moveToFirst();
// build media object
Media media = new Media();
media.setId(Integer.parseInt(cursor.getString(0)));
media.setType(cursor.getString(1));
media.setTitle(cursor.getString(2));
media.setAuthor(cursor.getString(3));
//log
Log.d("getMedia("+id+")", media.toString());
// return media
return media;
}
//GET ALL MEDIA
public List<Media> getAllMedia() {
List<Media> medias = new LinkedList<Media>();
// build the query
String query = "SELECT * FROM " + TABLE_MEDIA;
// get reference to writable DB
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(query, null);
// go over each row, build media and add it to list
Media media = null;
if (cursor.moveToFirst()) {
do {
media = new Media();
media.setId(Integer.parseInt(cursor.getString(0)));
media.setType(cursor.getString(1));
media.setTitle(cursor.getString(2));
media.setAuthor(cursor.getString(3));
// Add media to media
medias.add(media);
} while (cursor.moveToNext());
}
Log.d("getAllMedia()", medias.toString());
// return media
return medias;
}
//UPDATE
public int updateMedia(Media media) {
// get reference to writable DB
SQLiteDatabase db = this.getWritableDatabase();
// create ContentValues to add key "column"/value
ContentValues values = new ContentValues();
values.put("type", media.getType()); // get title
values.put("title", media.getTitle()); // get title
values.put("author", media.getAuthor()); // get author
// updating row
int i = db.update(TABLE_MEDIA, //table
values, // column/value
KEY_ID+" = ?", // selections
new String[] { String.valueOf(media.getId()) }); //selection args
// close
db.close();
return i;
}
//DELETE
public void deleteMedia(Media media) {
// get reference to writable DB
SQLiteDatabase db = this.getWritableDatabase();
// delete
db.delete(TABLE_MEDIA, //table name
KEY_ID+" = ?", // selections
new String[] { String.valueOf(media.getId()) }); //selections args
// close
db.close();
//log
Log.d("deleteMedia", media.toString());
}
}
Media object class
public class Media {
private int id;
private String type;
private String title;
private String author;
public Media(){}
public Media(String type, String title, String author) {
super();
this.type = type;
this.title = title;
this.author = author;
}
//getters & setters
// getting ID
public int getId(){
return this.id;
}
// setting id
public void setId(int id){
this.id = id;
}
// getting type
public String getType(){
return this.type;
}
// setting title
public void setType(String type){
this.type = type;
}
// getting title
public String getTitle(){
return this.title;
}
// setting title
public void setTitle(String title){
this.title = title;
}
// getting author
public String getAuthor(){
return this.author;
}
// setting author
public void setAuthor(String author){
this.author = author;
}
#Override
public String toString() {
return "Media [id=" + id + ", type=" + type + ",title=" + title + ", author=" + author
+ "]";
}
}
Main Activity
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MySQLiteHelper db = new MySQLiteHelper(this);
/**
* CRUD Operations
* */
// add Media
db.addMedia(new Media("Book", "Android Application Development Cookbook", "Wei Meng Lee"));
db.addMedia(new Media("Book", "Android Programming: The Big Nerd Ranch Guide", "Bill Phillips and Brian Hardy"));
db.addMedia(new Media("Book", "Learn Android App Development", "Wallace Jackson"));
// get all media
List<Media> list = db.getAllMedia();
// delete one media
db.deleteMedia(list.get(0));
// get all media
db.getAllMedia();
}
}
There's an error in you table creation:
String CREATE_MEDIA_TABLE = "CREATE TABLE media ( " +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"tyoe TYPE, "+
"title TEXT, "+
"author TEXT )";
TYPE is not a valid SQLlite Data Type.
Please refer to this page: https://www.sqlite.org/datatype3.html
I'd write your table creation as
String CREATE_MEDIA_TABLE = "CREATE TABLE media (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"tyoe TEXT, "+
"title TEXT, "+
"author TEXT)";