Search code examples
javaarraysfileandroid-room

Why can't my phone app open a file it stored in a Room Database?


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;
    }
}

Solution

  • 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() + "\n\t 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.