For some reason even after following some parts of the guide, I wasn’t able to insert a new entry to my database. retrival is okay. I don’t really want to do the respitory and viewmodel boilerplates as mentioned in the guide
I have put the relevant dependencies in build.gradle already. I am using Java 11.
TemiPatrolRoomDatabase.java:
@Database(entities = {AudioFile.class},version = 1) public abstract class TemiPatrolRoomDatabase extends RoomDatabase { public abstract TemiPatrolDAO temiPatrolDAO(); private static volatile TemiPatrolRoomDatabase INSTANCE; private static final int NUMBER_OF_THREADS = 8; public static final ExecutorService databaseWriteExecutor = Executors.newFixedThreadPool(NUMBER_OF_THREADS); public static TemiPatrolRoomDatabase getDatabase(final Context context) { if (INSTANCE == null) { synchronized (TemiPatrolRoomDatabase.class) { if (INSTANCE == null) { INSTANCE = Room.databaseBuilder(context.getApplicationContext(), TemiPatrolRoomDatabase.class, "temiPatrolDatabase") .build(); } } } return INSTANCE; } }
TemiPatrolDAO.java
@Dao public interface TemiPatrolDAO { @Query("SELECT * from AudioFile") List<AudioFile> getAll(); @Insert(entity = AudioFile.class,onConflict = OnConflictStrategy.IGNORE) void insertAll(AudioFile... audioFiles); @Insert(entity = AudioFile.class,onConflict = OnConflictStrategy.IGNORE) void insert(AudioFile audioFile); @Delete void delete(AudioFile audioFile); }
SettingsFragment.java:
public void onCreate(Bundle savedInstanceState) { .... addAudioFileResultLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { Intent resultingIntent = result.getData(); assert resultingIntent != null; var uri = resultingIntent.getData(); var cursor = requireActivity() .getContentResolver() .query(uri, null, null, null, null); int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); cursor.moveToFirst(); var fileName = cursor.getString(nameIndex); Toast.makeText(getActivity(), "audio added:" + fileName, Toast.LENGTH_LONG) .show(); var audioFile = new AudioFile(uri.getPath(), fileName); var future = TemiPatrolRoomDatabase.databaseWriteExecutor.submit(() -> { Log.d(TAG, "audio name to be inserted:"+audioFile.getName()); temiPatrolDAO.insert(audioFile); var a = (ArrayList<AudioFile>) temiPatrolDAO.getAll(); Log.d(TAG, "after insertion"); for (var a1 : a) { Log.d(TAG, "file name:" + a1.getName()); } }); while (!future.isDone()){} Log.d(TAG, "after future of inserting data is done"); audioFiles.add(audioFile); customAdapter.notifyDataSetChanged(); cursor.close(); } ); var context = requireActivity().getApplicationContext(); var database = TemiPatrolRoomDatabase.getDatabase(context); temiPatrolDAO = database.temiPatrolDAO(); }
AudioFile.java
@SuppressWarnings("ALL") @Entity public class AudioFile { @PrimaryKey private int audioFileID; private String uriPath; private String name; public AudioFile(String uriPath, String name) { this.setUriPath(uriPath); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAudioFileID() { return audioFileID; } public void setAudioFileID(int audioFileID) { this.audioFileID = audioFileID; } public String getUriPath() { return uriPath; } public void setUriPath(String uriPath) { this.uriPath = uriPath; } }
Advertisement
Answer
One issue you have is with private int audioFileID;
and that you are not specifying a value for the audioFileID when you use :-
var audioFile = new AudioFile(uri.getPath(), fileName);
and then
temiPatrolDAO.insert(audioFile);
As an int defaults to 0, then without setting audioFileID, any but the first insert will have a UNIQUE conflict as the primary key MUST be a unique value within the table. An onConflictStrategy of IGNORE will simply ignore the conflict BUT and carry on without inserting the row.
I would suggest changing to use either :-
@Entity public class AudioFile { @PrimaryKey private Long audioFileID; //<<<<< CHANGED private String uriPath; private String name; public AudioFile(String uriPath, String name) { this.setUriPath(uriPath); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public long getAudioFileID() { //<<<<< CHANGED return audioFileID; } public void setAudioFileID(long audioFileID) { //<<<<< CHANGED this.audioFileID = audioFileID; } public String getUriPath() { return uriPath; } public void setUriPath(String uriPath) { this.uriPath = uriPath; } }
The reason is that an Object (Long) will default to null and in that case Room interprets (if the above is used) this as the value not being specified so the column and it’s value is omitted from the INSERT.
- I suggest Long as the value can be Long. However, Integer could be used, but could be an issue if the number of rows is greater than what an int can hold (as such I will always code Long/long for an ID column).
Alternately, as the uriPath is likely to be unique, you could have this as the primary key and remove the audioFileID column e.g. :-
@Entity public class AudioFile { @PrimaryKey private String uriPath; private String name; public AudioFile(String uriPath, String name) { this.setUriPath(uriPath); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getUriPath() { return uriPath; } public void setUriPath(String uriPath) { this.uriPath = uriPath; } }
- the disadvantage (possibly not of importance) with this is that as the column is not an INTEGER then it is not an alias of the rowid (a special column that in Room will always exist). Access via the rowid column can be significantly fatser (up to twice as fast). However, this is pretty much insignificant for smaller tables.
Another problem that you may encounter is after obtaining the Cursor (which will never be null if returned from an SQLiteDatabase (and therefore SupportSQLiteDatabase) method). If the Cursor is empty and you don’t check the result (true if moved, false if not (aka no rows)) of the moveToFirst
then you will get an exception. So you should have something like:-
if (cursor.moveToFirst()) { var fileName = cursor.getString(nameIndex); Toast.makeText(getActivity(), "audio added:" + fileName, Toast.LENGTH_LONG) .show(); var audioFile = new AudioFile(uri.getPath(), fileName); var future = TemiPatrolRoomDatabase.databaseWriteExecutor.submit(() -> { Log.d(TAG, "audio name to be inserted:"+audioFile.getName()); temiPatrolDAO.insert(audioFile); var a = (ArrayList<AudioFile>) temiPatrolDAO.getAll(); Log.d(TAG, "after insertion"); for (var a1 : a) { Log.d(TAG, "file name:" + a1.getName()); } }); while (!future.isDone()){} Log.d(TAG, "after future of inserting data is done"); audioFiles.add(audioFile); customAdapter.notifyDataSetChanged(); } cursor.close();
- Note I haven’t checked the above code, so it may have some errors, it’s the principle that matters.