Skip to content
Advertisement

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

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.

User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement