出于某种原因,我的 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;
    }
}

我已将 @PrimaryKeyaudioFileID 更改为 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();
  • 注意上面的代码我没有检查过,所以可能会有一些错误,重要的是原理。