出于某种原因,我的 Android room DAO 插件在 Temi Robot 上不起作用
For some reason my Android room DAO insert doesn't work on Temi Robot
出于某种原因 even after following some parts of the guide,我无法向我的数据库中插入新条目。恢复没问题。我真的不想做指南中提到的 respitory 和 viewmodel 样板
我已经把相关的依赖放在build.gradle里了。
我正在使用 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;
}
}
我已将 @PrimaryKey
从 audioFileID
更改为 uriPath
,
然后我从 Temi Robot 中删除了我的应用程序
adb shell pm list packages
adb uninstall <my-package>
最后我重新安装了我的应用程序,它运行正常了。
@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;
}
您遇到的一个问题是 private int audioFileID;
,您在使用 :-
时没有指定 audioFileID 的值
var audioFile = new AudioFile(uri.getPath(), fileName);
然后
temiPatrolDAO.insert(audioFile);
由于 int 默认为 0,因此在不设置 audioFileID 的情况下,除了第一个插入之外的任何内容都将具有 UNIQUE 冲突,因为主键必须是 table 中的唯一值。 IGNORE 的 onConflictStrategy 将简单地忽略冲突 BUT 并继续而不插入行。
我建议改为使用 :-
@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;
}
}
原因是对象(长整型)将默认为 null,在这种情况下,Room 将其解释为(如果使用上述)作为未指定的值,因此从 INSERT 中省略列及其值。
- 我建议Long,因为值可以是Long。但是,可以使用 Integer,但如果行数大于 int 可以容纳的行数,则可能会出现问题(因此我将始终为 ID 列编码 Long/long)。
或者,由于 uriPath 可能是唯一的,您可以将其作为主键并删除 audioFileID 列,例如:-
@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;
}
}
- 缺点(可能不重要)因为该列不是整数,所以它不是 rowid 的别名(Room 中的一个特殊列)永远存在)。通过 rowid 列访问可以明显更胖(快两倍)。然而,这对于较小的 tables 来说几乎微不足道。
您可能遇到的另一个问题是在获取 Cursor 之后(如果从 SQLiteDatabase(以及 SupportSQLiteDatabase)方法返回,则 Cursor 永远不会为 null)。如果 Cursor 为空并且您不检查 moveToFirst
的结果(如果移动则为 true,如果没有移动则为 false(也就是没有行))那么您将得到一个异常。所以你应该有这样的东西:-
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();
- 注意上面的代码我没有检查过,所以可能会有一些错误,重要的是原理。
出于某种原因 even after following some parts of the guide,我无法向我的数据库中插入新条目。恢复没问题。我真的不想做指南中提到的 respitory 和 viewmodel 样板
我已经把相关的依赖放在build.gradle里了。 我正在使用 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;
}
}
我已将 @PrimaryKey
从 audioFileID
更改为 uriPath
,
然后我从 Temi Robot 中删除了我的应用程序
adb shell pm list packages
adb uninstall <my-package>
最后我重新安装了我的应用程序,它运行正常了。
@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;
}
您遇到的一个问题是 private int audioFileID;
,您在使用 :-
var audioFile = new AudioFile(uri.getPath(), fileName);
然后
temiPatrolDAO.insert(audioFile);
由于 int 默认为 0,因此在不设置 audioFileID 的情况下,除了第一个插入之外的任何内容都将具有 UNIQUE 冲突,因为主键必须是 table 中的唯一值。 IGNORE 的 onConflictStrategy 将简单地忽略冲突 BUT 并继续而不插入行。
我建议改为使用 :-
@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;
}
}
原因是对象(长整型)将默认为 null,在这种情况下,Room 将其解释为(如果使用上述)作为未指定的值,因此从 INSERT 中省略列及其值。
- 我建议Long,因为值可以是Long。但是,可以使用 Integer,但如果行数大于 int 可以容纳的行数,则可能会出现问题(因此我将始终为 ID 列编码 Long/long)。
或者,由于 uriPath 可能是唯一的,您可以将其作为主键并删除 audioFileID 列,例如:-
@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;
}
}
- 缺点(可能不重要)因为该列不是整数,所以它不是 rowid 的别名(Room 中的一个特殊列)永远存在)。通过 rowid 列访问可以明显更胖(快两倍)。然而,这对于较小的 tables 来说几乎微不足道。
您可能遇到的另一个问题是在获取 Cursor 之后(如果从 SQLiteDatabase(以及 SupportSQLiteDatabase)方法返回,则 Cursor 永远不会为 null)。如果 Cursor 为空并且您不检查 moveToFirst
的结果(如果移动则为 true,如果没有移动则为 false(也就是没有行))那么您将得到一个异常。所以你应该有这样的东西:-
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();
- 注意上面的代码我没有检查过,所以可能会有一些错误,重要的是原理。