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

i keep getting increased repetitive onChange passes on my observables with database changes

我有一个词典应用程序,我想在其中将现有的同义词分配给词典中的一个词。为此,我使用的是在单词和同义词 table 之间使用 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));

}

然后我给用户机会将同义词 table 中的一个现有的、未分配的同义词关联到单词 table。

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

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 进来的地方吗?

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

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

此处:

 wordSynonymJoinViewModel.getSynonymsByNotWord(wordId).observe(SynonymActivity.this, new Observer<List<SynonymWithWords>>() {

您正在监视同义词,但在观察的内部,您会显示一个对话框并允许添加更多同义词。每次添加新的同义词时,它都会创建一个新的 AlertDialog。

所以这就是为什么您必须在每个对话框中按取消键的原因。

要解决这个问题,您可以将 AlertDialog 分配给一个字段并使用 isShowing() 方法来决定是否要显示另一个对话框(即,如果一个对话框已经显示,则不显示另一个。

https://developer.android.com/reference/android/app/Dialog.html#isShowing()


至于您的所有其他问题,很抱歉,我无法展开包装。

不过,我可以分享我的想法:

I want to assign existing synonyms to a word in the dictionary.

也许忘记了数据库开始并创建一个内存解决方案。 稍后您可以将其更改为持久化。

在内存中,该结构看起来像字典单词和同义词查找的哈希表 Map<String, List<String>>

这个地图将在一个名为 Repository 的 class 中,它以某种方式公开供您观察和更新它(RxJava Observable)或像您已经拥有的 LiveData。

你的 Fragment 会观察这个 Map,使用你想要的 MVVM 或 MVP 在 RecyclerView 中显示它。

你在 RecyclerView 的每一行上都有一个点击监听器来添加一个新的同义词。单击打开对话框(或新的 activity/fragment)。在用户键入同义词后,您将通过存储库将其保存到您的地图 - 因此原始观察者将更新 RecyclerView。

你不应该进入打开多个对话框的循环状态:/

希望对您有所帮助,说实话,听起来您的方向是对的,只需要再努力一点。

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


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

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

订阅即可

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

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


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

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

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

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

您对 ViewModel 执行操作并通过回调接收响应。这非常适合回调。但是,您不能对 LiveData 执行相同的操作。当使用 LiveData 时,视图层不期望每个操作都会有响应。相反,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);
}

也可以,但是您通常不会在每次需要视图模型时都从 ViewModelProviders 获取 ViewModel。您应该只在 onCreate 处获取一个视图模型,将其保存为 activity 实例变量,并在 activity.

的其余部分使用相同的实例