Android Room - 如何在插入之前检查是否已经存在同名的实体?

问题描述 投票:1回答:1

我正在使用mvvm模式和android room创建一个应用程序,但我在验证用户输入时遇到了一些麻烦。当用户想在应用程序中添加一个原料时,他们需要为这个原料输入一个名称。我想让应用通知用户这个名字是否已经被使用。我已经尝试了一些使用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);

最后是用来创建Ingredient的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;
    }
}

viewmodel中的onXXEntered()函数与片段中的textViews链接,当按下保存按钮时,会调用saveIngredient()函数。XXError LiveData的用于向用户显示错误。

真正的问题在于LiveData是异步的,用户可以在LiveData包含来自数据库的结果之前更改输入并点击保存按钮。如果我想在保存输入时进行检查,那么在检查之前,"添加活动 "就已经结束了。

再次,非常感谢任何帮助。

java android mvvm android-room android-livedata
1个回答
0
投票

在我最近的一个项目中,我不得不做类似的事情。我所做的是

  1. Room不能用SQLite创建列 独特的 约束(如果它不是PrimaryKey--这是你的情况)。所以不要在你的应用程序代码中使用Room初始化数据库。相反,在你的应用程序之外创建一个数据库文件。添加一个 独特的 约束,然后在'name'列上添加数据库文件。然后在你的项目中添加数据库文件,在 资产 文件夹。(例如,在assets里创建一个子文件夹--'db_files',然后把你预先创建的数据库文件复制到这个文件夹下)

  2. 我猜你的@DataBase类使用了Singleton模式。用下面的方法替换你的'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;
    }

这将在应用程序的数据库文件路径下创建一个预打包的数据库文件的副本。

  1. 有了唯一的约束条件,你的 "getInstance() "方法就会在应用程序的数据库文件路径下创建一个预包装的数据库文件副本。@插入@Update 注解的方法将尊重约束条件,并将抛出一个 SQLiteConstraintException 如果你试图插入一个以前使用过的名字。你可以捕获这个异常,并按照你的意愿将其传递给你的View或ViewModel(我实现了一个简单的集中式事件发布组件)。

我希望这能帮助你。

© www.soinside.com 2019 - 2024. All rights reserved.