Problem: My Android phone app can open various file types stored in an Android Room
pre-populated SQLite
database but it cannot open files the app itself has added to the pre-populated database (except it can open .txt files). I believe the issue is probably with how the I coded the copying and conversion of a selected file to byte[]
data. The app is java based, and I have done this in Java before in a desktop app, so I just can’t seem to find the issue. Maybe it is a permission issue, I’m just not sure and someone standing outside looking in may see what I can’t.
What I have tried: Since the app can open various existing pre-populated files successfully from the DB, I’ve concentrated on and stepped through methods writing files to the DB. I’m not receiving any errors. I suspect it may just be minor issue since I can’t seem to see it.
What I’m trying to do: I’m trying to emulate the desktop version of this app into a Android phone version. I know it’s not recommended or common practice to populate files to a DB, but this app needs to be able to read and write files to the DB supporting it. This will be a full range of file types like the desktop version (e.g., pics, docs, audio, video, etc.). However, as I stated above, .txt files seem to have no issue. The user can select files stored on their phone into a table that captures the fileName
and filePath
to a TableRow
in a TableLayout
. Below are methods involved. The plan is to refactor functionality once I get it working:
Capturing the full path and filename for each row – Uses the captured filepath to convert to a byte[]
to store the data. The filename and file byte data are stored in a Files table, example, Files(fileName, fileData(byte[])). Each file is added to an ArrayList<Files>
which the method returns
public static List<Files> captureNoteFiles(TableLayout table){ List<Files> noteFiles = new ArrayList<>(); int i = table.getChildCount(); if(i>1){ for (int itr = 1; itr<i; itr++) { // iterating through indexes TableRow tr = (TableRow) table.getChildAt(itr); TextView tv = (TextView) tr.getChildAt(1); // 1 is the file path position File f = new File(tv.getText().toString()); String n = f.getName(); try { FileInputStream fis = new FileInputStream(f.getPath()); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] buf = new byte[1024]; for (int read; (read = fis.read(buf)) != -1; ) { bos.write(buf, 0, read); } fis.close(); noteFiles.add(new Files(0, n, bos.toByteArray())); } catch (Exception e) { e.printStackTrace(); Log.d("Input File", e.toString()); } } } return noteFiles; }
Iteration of the ArrayList – The ArrayList<Files>
is iterated and populated to the Files table and an ID capture to associate those files with a particular note of reference.
public static void addNewNoteFiles(int noteID, List<Files> nf){ if(nf.size()>0) { for (Files f : nf) { long id = rdb.getFilesDao().addFile(f); rdb.getFilesByNoteDao().insert(new FilesByNote(noteID, (int) id)); } } }
Files Entity
@Entity(tableName = "Files") public class Files implements Parcelable { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "FileID") private int fileID; @ColumnInfo(name = "FileName") private String fileName; @TypeConverters(FileTypeConverter.class) @ColumnInfo(name = "FileData", typeAffinity = ColumnInfo.TEXT) private byte[] fileData; @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) public Files(int fileID, String fileName, byte[] fileData){ this.fileID = fileID; this.fileName = fileName; this.fileData = fileData; } }
Advertisement
Answer
First you are assuming that an insert works as per :-
long id = rdb.getFilesDao().addFile(f); rdb.getFilesByNoteDao().insert(new FilesByNote(noteID, (int) id));
What if the row isn’t inserted? and returns an id of -1?
So I’d suggest adding getters to the Files class such as :-
public int getFileID() { return fileID; } public String getFileName() { return fileName; } public byte[] getFileData() { return fileData; }
and then add the following to FilesDao :-
@Query("SELECT coalesce(length(FileData)) FROM Files WHERE FileID=:fileId") abstract long getFilesDataLength(long fileId);
and then amending the addNewNoteFiles to be :-
public static void addNewNoteFiles(int noteID, List<Files> nf){ final String TAG = "ADDNEWNOTE"; if(nf.size()>0) { for (Files f : nf) { long id = rdb.getFilesDao().addFile(f); if (id > 0) { long lengthOfFileData = rdb.getFilesDao().getFilesDataLength(id); Log.d(TAG, "Inserted File = " + f.getFileName() + " DataLength = " + f.getFileData().length + " ID = " + f.getFileID() + " Length of Stored Data = " + lengthOfFileData); if (f.getFileData().length != lengthOfFileData) { Log.d(TAG,"WARNING FileData length MISMATCH for File = " + f.getFileName() + "nt Expected " + f.getFileData().length + " Found " + lengthOfFileData); } rdb.getFilesByNoteDao().insert(new FilesByNote(noteID, (int) id)); } else { Log.d(TAG,"NOT INSERTED File = " + f.getFileName()); } } } }
Run and check the log. Are all the files inserted? Do the lengths match? Are the lengths as expected (if all 0 lengths, or some, then obviously something is amiss when building the ByteArrayOutputStream)
You may wish to add similar for inserting the FilesByNote i.e. have the insert Dao return a long (it returns the rowid) and check if the value is > 0.
- You may wonder what rowid is. Well it’s a normally hidden column, perhaps hidden as it would appear that FilesByNotes is an associative table mapping(associating) Note(s) with Files and as such has a composite primary key NoteId and FileId which is not an alias of the rowid, so rowid will be hidden as such. However, the value will be auto-generated or -1 if no row is inserted.
- ALL tables, with the exception of tables defined with WITHOUT ROWID, have a rowid column. Room does not allow thee definition of WITHOUT ROWID tables.
You wouldn’t be concerned about the value if it’s greater than 0, just that it is greater than 0 and thus a row was inserted.
The above may help to determine any issues encountered when inserting the data. If there are none found then the issue is else where.