android - 随着数据库更改,我不断增加对我的可观察对象的重复 onChange 传递

标签 android mvvm android-room android-livedata

我有一个字典应用程序,我想将现有的同义词分配给字典中的一个词。为此,我使用的是在单词表和同义词表之间使用 M:N 关系。

实体:

@Entity(tableName = "word_table",
indices = @Index(value = "word", unique = true))
public class Word {
    @PrimaryKey(autoGenerate = true)
    private long id;
    private String word;

    @Ignore
    public Word(String word) {
        this.word = word;
    }

    public Word(long id, String word) {
        this.id = id;
        this.word = word;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getWord() {
        return word;
    }

    public void setWord(String word) {
        this.word = word;
    }
}

@Entity(tableName = "synonym_table")
public class Synonym {
    @PrimaryKey(autoGenerate = true)
    private long sid;
    private String synonym;

    @Ignore
    public Synonym(String synonym) {
        this.synonym = synonym;
    }

    public Synonym(long sid, String synonym) {
        this.sid = sid;
        this.synonym = synonym;
    }

    public long getSid() {
        return sid;
    }

    public void setSid(long id) {
        this.sid = sid;
    }

    public String getSynonym() {
        return synonym;
    }

    public void setSynonym(String synonym) {
        this.synonym = synonym;
    }
}

@Entity(tableName = "word_synonym_join_table",
primaryKeys= {"word_id" , "synonym_id"},
foreignKeys = {@ForeignKey(entity = Word.class, parentColumns = "id", childColumns = "word_id"),
        @ForeignKey(entity = Synonym.class, parentColumns = "sid", childColumns = "synonym_id")})
public class WordSynonymJoin {
    @ColumnInfo(name = "word_id")
    private long wordId;
    @ColumnInfo(name = "synonym_id")
    private long synonymId;

    public WordSynonymJoin(long wordId, long synonymId) {
        this.wordId = wordId;
        this.synonymId = synonymId;
    }

    public long getWordId() {
        return wordId;
    }

    public void setWordId(long wordId) {
        this.wordId = wordId;
    }

    public long getSynonymId() {
        return synonymId;
    }

    public void setSynonymId(long synonymId) {
        this.synonymId = synonymId;
    }
}

为了检索 Word 和相关同义词的数据,我创建了一个名为 WordWithSynonyms 的 POJO。

public class WordWithSynonyms {
    @Embedded
    public Word word;

    @Embedded
    public WordSynonymJoin wordSynonymJoin;
}

道如下:

@Dao
public interface WordDao {
    @Query("SELECT * FROM word_table")
    public LiveData<List<Word>> getAllWords();

    @Query("SELECT * FROM word_table WHERE id =:wordId")
    public LiveData<List<Word>> getWordById(long wordId);

    @Query("SELECT * from word_table WHERE word =:value")
    public LiveData<List<Word>> getWordByValue(String value);

    @Insert
    public long insert(Word word);

    @Delete
    public void delete(Word word);

    @Update
    public void update(Word word);

    @Query("DELETE FROM word_table")
    public void deleteAll();
}

@Dao
public interface SynonymDao {
    @Query("SELECT * FROM synonym_table")
    public LiveData<List<Synonym>> getAllSynonyms();

    @Query("SELECT * FROM synonym_table WHERE synonym =:value")
    public LiveData<List<Synonym>> getSynonymByValue(String value);

    @Insert
    public void insert(Synonym synonym);

    @Delete
    public void delete(Synonym synonym);

    @Query("DELETE FROM synonym_table")
    public void deleteAll();
}

@Dao
public interface WordSynonymJoinDao {
    @Query("SELECT * FROM word_table INNER JOIN word_synonym_join_table " +
            "ON word_table.id = word_synonym_join_table.word_id " +
            "WHERE word_synonym_join_table.synonym_id =:synonymId")
    public LiveData<List<WordWithSynonyms>> getWordsBySynonym(long synonymId);

    @Query("SELECT * FROM synonym_table INNER JOIN word_synonym_join_table " +
            "ON synonym_table.sid = word_synonym_join_table.synonym_id " +
            "WHERE word_synonym_join_table.word_id =:wordId")
    public LiveData<List<SynonymWithWords>> getSynonymsByWord(long wordId);

    @Query("SELECT * FROM synonym_table INNER JOIN word_synonym_join_table " +
            "ON synonym_table.sid = word_synonym_join_table.synonym_id " +
            "WHERE word_synonym_join_table.word_id !=:wordId")
    public LiveData<List<SynonymWithWords>> getSynonymsByNotWord(long wordId);

    @Insert
    public void insert(WordSynonymJoin wordSynonymJoin);

    @Delete
    public void delete(WordSynonymJoin wordSynonymJoin);

    @Query("DELETE FROM word_synonym_join_table")
    public void deleteAll();
}

当我到达同义词 Activity 时,我传递 wordId 以通过 ViewModel 观察器检索该词的当前同义词。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_synonym);

    Intent intent = getIntent();
    wordId = Long.parseLong(intent.getStringExtra(EXTRA_WORD_ID));

    //SynonymViewModel synonymViewModel = ViewModelProviders.of(this).get(SynonymViewModel.class);
    WordSynonymJoinViewModel wordSynonymJoinViewModel = ViewModelProviders.of(this).get(WordSynonymJoinViewModel.class);

    //synonymAdapter = new SynonymListAdapter(this);
    synonymAdapter = new SynonymWithWordListAdapter(this);
    synonynRecyclerView = findViewById(R.id.recycler_view_syonym);

    if (wordId != 0) {
        wordSynonymJoinViewModel.getSynonymsByWord(wordId).observe(SynonymActivity.this, new Observer<List<SynonymWithWords>>() {
            @Override
            public void onChanged(@Nullable List<SynonymWithWords> synonymWithWords) {
                synonymAdapter.setSynonyms(synonymWithWords);
                synonymAdapter.notifyDataSetChanged();
            }
        });
    }

    synonynRecyclerView.setAdapter(synonymAdapter);
    synonynRecyclerView.setLayoutManager(new LinearLayoutManager(SynonymActivity.this));

}

然后我让用户有机会将同义词表中现有的、未分配的同义词关联到 Word 表。

我通过 AlertDialog 内的单独 ViewModel 观察器检索未使用和可用的同义词,AlertDialog 使用微调器使用另一个 ViewModel 观察器通过 WordSynonymJoin 表显示它们。 最后,当用户单击 AlertDialog 上的“确定”按钮时,在该 ViewModel 观察器内部,运行第三个 VieModel 观察器以实际插入到 WordSynonymJoin 表中。

case R.id.synonym_assign_synonym:
    final WordSynonymJoinViewModel wordSynonymJoinViewModel = ViewModelProviders.of(SynonymActivity.this).get(WordSynonymJoinViewModel.class);
    wordSynonymJoinViewModel.getSynonymsByNotWord(wordId).observe(SynonymActivity.this, new Observer<List<SynonymWithWords>>() {
        @Override
        public void onChanged(@Nullable List<SynonymWithWords> synonymWithWords) {

            List<String> synonymsNotAssignList = new ArrayList<>();
            for (SynonymWithWords sww : synonymWithWords)
                synonymsNotAssignList.add(sww.synonym.getSynonym());

            AlertDialog.Builder assignSynonymDialog = new AlertDialog.Builder(SynonymActivity.this);
            assignSynonymDialog.setTitle("Select New Category:");
            LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            View view = inflater.inflate(R.layout.alert_dialog_spinner_view, null);

            final Spinner synonymSpinner = (Spinner) view.findViewById(R.id.alert_dialog_spinner);

            final SynonymViewModel synonymViewModel = ViewModelProviders.of(SynonymActivity.this).get(SynonymViewModel.class);

            ArrayAdapter<String> spinnerAdapter = new ArrayAdapter(SynonymActivity.this, android.R.layout.simple_spinner_dropdown_item, synonymsNotAssignList);
            spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            synonymSpinner.setAdapter(spinnerAdapter);

            synonymSpinner.setSelection(synonymId);

            assignSynonymDialog.setView(view);
            assignSynonymDialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    final String synonymValue = synonymSpinner.getSelectedItem().toString();
                    // get new synonym id
                    synonymViewModel.getSynonymByValue(synonymValue).observe(SynonymActivity.this, new Observer<List<Synonym>>() {
                        @Override
                        public void onChanged(@Nullable List<Synonym> synonyms) {
                            long id = 0;


                            if (!synonyms.get(0).getSynonym().equals(synonymValue)) {
                                if (synonyms.size() > 1)
                                    Toast.makeText(SynonymActivity.this, "Query found " + synonyms.size() + " which is more than the one expected.", Toast.LENGTH_SHORT).show();
                            } else {
                                id = synonyms.get(0).getSid();
                            }
                            WordSynonymJoinViewModel wordSynonymJoinViewModel = ViewModelProviders.of(SynonymActivity.this).get(WordSynonymJoinViewModel.class);
                            wordSynonymJoinViewModel.insert(new WordSynonymJoin(wordId, id));
                        }
                    });

                }
            });
            assignSynonymDialog.setNegativeButton("Cancel", null);
            assignSynonymDialog.create();
            assignSynonymDialog.show();
        }
    });

    return true;

在第一遍中,一切似乎都运行良好。但是,在用户继续向单词添加新同义词的连续传递中,在添加每个同义词后需要多次单击 AlertDialog 的取消按钮才能退出。添加了 2 个同义词, 2 单击取消返回主 Activity 。添加了 3 个同义词,单击 3 次取消以删除 AlertDialog。

我对 MVVM 和 Room 持久性的整个概念非常陌生,所以我知道会有问题。下面是 AlertDialog 的代码,用于将现有的、未分配的同义词添加到当前单词。

我不喜欢为此使用了多少代码,但我无法用词来表达我的搜索,因此我无法找到绕过它的方法。

我的问题是:

为什么每次我输入 associate new synonym to the word 时,代码都会循环 +1?我是不是应该清理一些东西。

这个编码是否正确?

要完成看似微不足道的事情,这似乎是一项艰巨的工作。我想我错过了什么。我把它弄得异常复杂了吗?

这段代码看起来如此繁琐笨拙,我错过了什么?

这似乎是一种检索值的非常麻烦的方法,而且我真的认为我不需要观察上面运行的每个查询。也许我错了。

是否有研究方向可以帮助我更好地理解这一点?

这会是 Rx Java 的用武之地吗?

我当然可以根据需要提供更多代码。

如有任何帮助,我们将不胜感激。

最佳答案

TRDL:不要在 ON_CREATE 之外调用 .observe状态。


您犯了一个 LiveData 错误...但您并不孤单!这个错误是 StackOverflow 上最常见的 LiveData 错误:在 Activity#onCreate() 之外调用 .observe。这包括在点击监听器、onResume、广播接收器等中调用 .observe

我在大多数第一次使用 LiveData 的人身上看到的问题是,他们将 LiveData 视为回调,而实际上并非如此。 LiveData 是一个流。 LiveData 不会只通知一次。附加到 LiveDataObservers 将继续收到通知,直到他们取消订阅。此外,它意味着在生命周期开始时订阅(例如 Activity#onCreate 或 Fragment#onViewCreated)并在生命周期结束时取消订阅。 LiveData 自动处理取消订阅部分,因此您只需确保在 onCreate 中订阅即可。

你不断获得 +1 Dialog 的根本原因是之前的观察者没有死,每次重复同样的事情时你都会不断地向数据库添加新的订阅。尝试旋转手机,看看对话框的数量是否重置回 1。这是因为当您旋转屏幕并重新创建 Activity 时,所有之前的观察者都已取消订阅。

也许您可以调用 isShowing() 并查看是否有任何对话框打开,如另一个答案中所建议的那样。但是,这样做只是一种解决方法。如果它是 Toast 或您无法检查的其他内容怎么办?此外,您很幸运能够轻松发现此错误。您可能在其他地方有这个重复的观察者错误,而这些错误在视觉上并不明显。


所以我认为你已经知道如何使用LiveData,但你只需要知道如何正确地实现 react 模式。用一篇文章来解释太多了,但让我举一个简单的例子:

假设您有一个按钮,当您按下该按钮时,您会从数据库中获取一些数据。在类似回调的设计中,您经常调用 ViewModel 中的某些函数并传递回调实例。例如你有这个:

//ViewModel
void getSynonymsByNotWord(WordSynonymJoin word, Callback callback) { ... }

//Activity
void onClick(View v) {
    wordSynonymJoinViewModel.changeCurrentSysnonymsByNotWord(wordId, callback);
}

您对 ViewModel 执行操作并通过回调接收响应。这非常适合回调。但是,您不能对 LiveData 执行相同的操作。使用 LiveData 时,View 层不期望每个操作都会有响应。相反,View 层应该始终盲目地听取响应,甚至在单击按钮之前也是如此。

//ViewModel
private MutableLiveData wordQuery;
private Livedata synonymsByNotWord = Transformations.switchMap(wordQuery, word -> {
        return repository.getSynonymsByWord(word);
    });

LiveData getCurrentSynonymsByNotWord() {
    return synonymsByNotWord;
}

void changeCurrentSynonymsByNotWord(WordSynonymJoin word) {
    wordQuery.postValue(word);
}


//Activity
void onCreate() {
    wordSynonymJoinViewModel.getCurrentSynonymsByNotWord().observe(...);
} 

void onClick(View v) {
    wordSynonymJoinViewModel.changeCurrentSynonymsByNotWord(wordId);
}

也可以,但通常不会在每次需要 View 模型时从 ViewModelProviders 获取 ViewModel。您应该只在 onCreate 获取一个 View 模型,将其保存为 Activity 实例变量,并在 Activity 的其余部分使用相同的实例。

关于android - 随着数据库更改,我不断增加对我的可观察对象的重复 onChange 传递,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57366800/

相关文章:

android - 健身房锻炼日志应用程序的数据库架构

android - 房间数据库正在返回 1 个额外的对象

android - 处理 DVM 终止你的 Activity

android - 未知的 ListView 行为

android - 在对话框布局的属性中使用 "onclick"android

wpf - 将 FlowDocument 中的列表绑定(bind)到 List<MyClass>?

wpf - WPF MVVM并将 View 模型传递给 View

Java 平均值返回无穷大

ios - 在具有 MVVM 架构的 iOS 应用程序中调用 Web 服务的最佳位置是什么?

android - 房间迁移根据现有的值添加列