Android 房间 - 如何在插入之前检查是否已存在同名实体?
Android Room - How can I check if an entity with the same name already exists before inserting?
我正在使用 mvvm 模式和 android 空间创建一个应用程序,但我 运行 在验证用户输入时遇到了一些麻烦。当用户想要向应用程序添加成分时,他们需要输入该成分的名称。如果该名称已被使用,我希望该应用程序通知用户。我已经使用 Transformations.Map() 函数尝试了一些东西,但没有成功。
我对 mvvm 模式和 LiveData 还很陌生,我已经坚持了很长一段时间,所以任何建议都将不胜感激。
这是成分实体:
@Entity(tableName = "ingredient")
public class BaseIngredient {
@PrimaryKey(autoGenerate = true)
private int id;
private String name;
private String category;
@ColumnInfo(name = "cooking_time")
private int cookingTime;
@Ignore
public BaseIngredient() {
}
public BaseIngredient(int id, @NonNull String name, @NonNull String category, int cookingTime)
throws InvalidValueException {
this.id = id;
setName(name);
setCookingTime(cookingTime);
setCategory(category);
}
public void setName(String name) throws InvalidNameException {
if (name == null || name.isEmpty())
throw new InvalidNameException("Name is empty");
if (!name.matches("[A-z0-9]+( [A-z0-9]+)*"))
throw new InvalidNameException("Name contains invalid tokens");
this.name = name;
}
public void setCategory(String category) throws InvalidCategoryException {
if (category == null || category.isEmpty())
throw new InvalidCategoryException("Category is empty");
if (!category.matches("[A-z0-9]+"))
throw new InvalidCategoryException("Category contains invalid tokens");
this.category = category;
}
public void setCookingTime(int cookingTime) throws InvalidCookingTimeException {
if (cookingTime < 1)
throw new InvalidCookingTimeException("Time must be positive");
this.cookingTime = cookingTime;
}
/* getters */
public boolean isValid() {
return name != null && category != null && cookingTime != 0;
}
这是我正在使用的 IngredientRepository:
private IngredientDao ingredientDao;
private LiveData<List<BaseIngredient>> ingredients;
public IngredientRepository(Application application) {
LmcfyDatabase database = LmcfyDatabase.getDatabase(application.getApplicationContext());
ingredientDao = database.ingredientDao();
ingredients = ingredientDao.getAllIngredients();
}
public LiveData<List<BaseIngredient>> getAllIngredients() {
return ingredients;
}
public LiveData<List<BaseIngredient>> getIngredientsWithQuery(String query) {
return ingredientDao.getIngredientsWithQuery("%" + query + "%");
}
public void insert(BaseIngredient ingredient) {
LmcfyDatabase.databaseWriteExecutor.execute(() -> {
ingredientDao.insert(ingredient);
});
}
public LiveData<Integer> getIngredientsWithNameCount(String name) {
return ingredientDao.getIngredientsWithNameCount(name);
}
IngredientDao:
@Insert(onConflict = OnConflictStrategy.IGNORE, entity = BaseIngredient.class)
long insert(BaseIngredient ingredient);
@Delete(entity = BaseIngredient.class)
void delete(BaseIngredient ingredient);
@Query("SELECT * FROM ingredient")
LiveData<List<BaseIngredient>> getAllIngredients();
@Query("SELECT * FROM ingredient WHERE name LIKE :query")
LiveData<List<BaseIngredient>> getIngredientsWithQuery(String query);
@Query("SELECT COUNT(id) FROM ingredient WHERE name LIKE :name")
LiveData<Integer> getIngredientsWithNameCount(String name);
最后是用于创建成分的 ViewModel
private final IngredientRepository repository;
private final BaseIngredient ingredient;
private final MutableLiveData<String> nameError;
private final MutableLiveData<String> categoryError;
private final MutableLiveData<String> cookingTimeError;
private final MutableLiveData<Boolean> ingredientValidStatus;
public AddIngredientViewModel(@NonNull Application application) {
super(application);
repository = new IngredientRepository(application);
ingredient = new BaseIngredient();
nameError = new MutableLiveData<>();
categoryError = new MutableLiveData<>();
cookingTimeError = new MutableLiveData<>();
ingredientValidStatus = new MutableLiveData<>();
}
public void onNameEntered(String name) {
try {
ingredient.setName(name);
nameError.setValue(null);
} catch (InvalidNameException e) {
nameError.setValue(e.getMessage());
} finally {
updateIngredientValid();
}
}
public void onCategoryEntered(String category) {
try {
ingredient.setCategory(category);
categoryError.setValue(null);
} catch (InvalidCategoryException e) {
categoryError.setValue(e.getMessage());
} finally {
updateIngredientValid();
}
}
public void onCookingTimeEntered(int cookingTime) {
try {
ingredient.setCookingTime(cookingTime);
cookingTimeError.setValue(null);
} catch (InvalidCookingTimeException e) {
cookingTimeError.setValue(e.getMessage());
} finally {
updateIngredientValid();
}
}
private void updateIngredientValid() {
ingredientValidStatus.setValue(ingredient.isValid());
}
public boolean saveIngredient() {
if (ingredient.isValid()) {
Log.d(getClass().getName(), "saveIngredient: Ingredient is valid");
repository.insert(ingredient);
return true;
} else {
Log.d(getClass().getName(), "saveIngredient: Ingredient is invalid");
return false;
}
}
视图模型中的 onXXEntered() 函数链接到片段中的文本视图,按下保存按钮时调用 saveIngredient() 函数。 XXError LiveData 用于向用户显示错误。
真正的问题在于 LiveData 是异步的,用户可以在 LiveData 包含来自数据库的结果之前更改他们的输入并单击保存按钮。如果我想在保存时检查输入,'add activity' 在检查完成之前就已经完成了。
再一次,我们将不胜感激。
我不得不在我最近的一个项目中做类似的事情。我所做的是:
Room 无法使用 SQLite Unique 约束创建列(如果它不是 PrimaryKey - 这是您的情况)。所以不要使用 Room 在您的应用程序代码中初始化数据库。相反,在您的应用程序外部创建一个数据库文件。在 'name' 列上添加 Unique 约束。然后在项目中的 assets 文件夹下添加数据库文件。 (例如,在 assets 中创建一个子文件夹 - 'db_files' - 并将您预先创建的数据库文件复制到该文件夹下)
我猜你为你的@DataBase 使用了单例模式class。将您的 'getInstance()' 方法替换为以下内容:
public static MyDB getInstance(final Context context) {
if(INSTANCE == null) {
synchronized (AVListDB.class) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
MyDB.class,"myDB.db")
.createFromAsset( "db/myDB.db")
.build();
}
}
return INSTANCE;
}
这会在应用程序数据库文件路径下创建预打包数据库文件的副本。
- 有了唯一约束,你的 @Insert 和 @Update 注释方法将遵守约束条件,并抛出一个 SQLiteConstraintException 如果您尝试插入以前使用过的名称。您可以捕获此异常,并根据需要将其传递给您的 View 或 ViewModel(我实现了一个简单的集中式事件发布者组件)。
希望对您有所帮助。
我正在使用 mvvm 模式和 android 空间创建一个应用程序,但我 运行 在验证用户输入时遇到了一些麻烦。当用户想要向应用程序添加成分时,他们需要输入该成分的名称。如果该名称已被使用,我希望该应用程序通知用户。我已经使用 Transformations.Map() 函数尝试了一些东西,但没有成功。
我对 mvvm 模式和 LiveData 还很陌生,我已经坚持了很长一段时间,所以任何建议都将不胜感激。
这是成分实体:
@Entity(tableName = "ingredient")
public class BaseIngredient {
@PrimaryKey(autoGenerate = true)
private int id;
private String name;
private String category;
@ColumnInfo(name = "cooking_time")
private int cookingTime;
@Ignore
public BaseIngredient() {
}
public BaseIngredient(int id, @NonNull String name, @NonNull String category, int cookingTime)
throws InvalidValueException {
this.id = id;
setName(name);
setCookingTime(cookingTime);
setCategory(category);
}
public void setName(String name) throws InvalidNameException {
if (name == null || name.isEmpty())
throw new InvalidNameException("Name is empty");
if (!name.matches("[A-z0-9]+( [A-z0-9]+)*"))
throw new InvalidNameException("Name contains invalid tokens");
this.name = name;
}
public void setCategory(String category) throws InvalidCategoryException {
if (category == null || category.isEmpty())
throw new InvalidCategoryException("Category is empty");
if (!category.matches("[A-z0-9]+"))
throw new InvalidCategoryException("Category contains invalid tokens");
this.category = category;
}
public void setCookingTime(int cookingTime) throws InvalidCookingTimeException {
if (cookingTime < 1)
throw new InvalidCookingTimeException("Time must be positive");
this.cookingTime = cookingTime;
}
/* getters */
public boolean isValid() {
return name != null && category != null && cookingTime != 0;
}
这是我正在使用的 IngredientRepository:
private IngredientDao ingredientDao;
private LiveData<List<BaseIngredient>> ingredients;
public IngredientRepository(Application application) {
LmcfyDatabase database = LmcfyDatabase.getDatabase(application.getApplicationContext());
ingredientDao = database.ingredientDao();
ingredients = ingredientDao.getAllIngredients();
}
public LiveData<List<BaseIngredient>> getAllIngredients() {
return ingredients;
}
public LiveData<List<BaseIngredient>> getIngredientsWithQuery(String query) {
return ingredientDao.getIngredientsWithQuery("%" + query + "%");
}
public void insert(BaseIngredient ingredient) {
LmcfyDatabase.databaseWriteExecutor.execute(() -> {
ingredientDao.insert(ingredient);
});
}
public LiveData<Integer> getIngredientsWithNameCount(String name) {
return ingredientDao.getIngredientsWithNameCount(name);
}
IngredientDao:
@Insert(onConflict = OnConflictStrategy.IGNORE, entity = BaseIngredient.class)
long insert(BaseIngredient ingredient);
@Delete(entity = BaseIngredient.class)
void delete(BaseIngredient ingredient);
@Query("SELECT * FROM ingredient")
LiveData<List<BaseIngredient>> getAllIngredients();
@Query("SELECT * FROM ingredient WHERE name LIKE :query")
LiveData<List<BaseIngredient>> getIngredientsWithQuery(String query);
@Query("SELECT COUNT(id) FROM ingredient WHERE name LIKE :name")
LiveData<Integer> getIngredientsWithNameCount(String name);
最后是用于创建成分的 ViewModel
private final IngredientRepository repository;
private final BaseIngredient ingredient;
private final MutableLiveData<String> nameError;
private final MutableLiveData<String> categoryError;
private final MutableLiveData<String> cookingTimeError;
private final MutableLiveData<Boolean> ingredientValidStatus;
public AddIngredientViewModel(@NonNull Application application) {
super(application);
repository = new IngredientRepository(application);
ingredient = new BaseIngredient();
nameError = new MutableLiveData<>();
categoryError = new MutableLiveData<>();
cookingTimeError = new MutableLiveData<>();
ingredientValidStatus = new MutableLiveData<>();
}
public void onNameEntered(String name) {
try {
ingredient.setName(name);
nameError.setValue(null);
} catch (InvalidNameException e) {
nameError.setValue(e.getMessage());
} finally {
updateIngredientValid();
}
}
public void onCategoryEntered(String category) {
try {
ingredient.setCategory(category);
categoryError.setValue(null);
} catch (InvalidCategoryException e) {
categoryError.setValue(e.getMessage());
} finally {
updateIngredientValid();
}
}
public void onCookingTimeEntered(int cookingTime) {
try {
ingredient.setCookingTime(cookingTime);
cookingTimeError.setValue(null);
} catch (InvalidCookingTimeException e) {
cookingTimeError.setValue(e.getMessage());
} finally {
updateIngredientValid();
}
}
private void updateIngredientValid() {
ingredientValidStatus.setValue(ingredient.isValid());
}
public boolean saveIngredient() {
if (ingredient.isValid()) {
Log.d(getClass().getName(), "saveIngredient: Ingredient is valid");
repository.insert(ingredient);
return true;
} else {
Log.d(getClass().getName(), "saveIngredient: Ingredient is invalid");
return false;
}
}
视图模型中的 onXXEntered() 函数链接到片段中的文本视图,按下保存按钮时调用 saveIngredient() 函数。 XXError LiveData 用于向用户显示错误。
真正的问题在于 LiveData 是异步的,用户可以在 LiveData 包含来自数据库的结果之前更改他们的输入并单击保存按钮。如果我想在保存时检查输入,'add activity' 在检查完成之前就已经完成了。
再一次,我们将不胜感激。
我不得不在我最近的一个项目中做类似的事情。我所做的是:
Room 无法使用 SQLite Unique 约束创建列(如果它不是 PrimaryKey - 这是您的情况)。所以不要使用 Room 在您的应用程序代码中初始化数据库。相反,在您的应用程序外部创建一个数据库文件。在 'name' 列上添加 Unique 约束。然后在项目中的 assets 文件夹下添加数据库文件。 (例如,在 assets 中创建一个子文件夹 - 'db_files' - 并将您预先创建的数据库文件复制到该文件夹下)
我猜你为你的@DataBase 使用了单例模式class。将您的 'getInstance()' 方法替换为以下内容:
public static MyDB getInstance(final Context context) {
if(INSTANCE == null) {
synchronized (AVListDB.class) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
MyDB.class,"myDB.db")
.createFromAsset( "db/myDB.db")
.build();
}
}
return INSTANCE;
}
这会在应用程序数据库文件路径下创建预打包数据库文件的副本。
- 有了唯一约束,你的 @Insert 和 @Update 注释方法将遵守约束条件,并抛出一个 SQLiteConstraintException 如果您尝试插入以前使用过的名称。您可以捕获此异常,并根据需要将其传递给您的 View 或 ViewModel(我实现了一个简单的集中式事件发布者组件)。
希望对您有所帮助。