Issue
I don't get why the entire list is refresh (and jump to the top) instead of only the new added word. It's negating the purpose of SubmitList().
I tried to change the repository to manage a local LiveData<List< Word>> getAllWords without room and it worked well.
Is there something in room that cause the LiveData<List< Word>> getAllWords to completely refresh ? If so how to avoid it, It's ugly.
WORD REPOSITORY
class WordRepository { private WordDao mWordDao; private LiveData<List<Word>> mAllWords; WordRepository(Application application) { WordRoomDatabase db = WordRoomDatabase.getDatabase(application); mWordDao = db.wordDao(); mAllWords = mWordDao.getAlphabetizedWords(); } LiveData<List<Word>> getAllWords() { return mAllWords; } void insert(Word word) { WordRoomDatabase.databaseWriteExecutor.execute(() -> { mWordDao.insert(word); }); } }
WORD VIEWMODEL
public class WordViewModel extends AndroidViewModel { private WordRepository mRepository; private final LiveData<List<Word>> mAllWords; public WordViewModel(Application application) { super(application); mRepository = new WordRepository(application); mAllWords = mRepository.getAllWords(); } LiveData<List<Word>> getAllWords() { return mAllWords; } void insert(Word word) { mRepository.insert(word); } }
WORD DAO
public interface WordDao { @Query("SELECT * FROM word_table ORDER BY word ASC") LiveData<List<Word>> getAlphabetizedWords(); @Insert(onConflict = OnConflictStrategy.IGNORE) void insert(Word word); @Query("DELETE FROM word_table") void deleteAll(); }
WORD ADAPTER
public class WordListAdapter extends ListAdapter<Word, WordViewHolder> { public WordListAdapter(@NonNull DiffUtil.ItemCallback<Word> diffCallback) { super(diffCallback); } @Override public WordViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return WordViewHolder.create(parent); } @Override public void onBindViewHolder(WordViewHolder holder, int position) { Word current = getItem(position); holder.bind(current.getWord()); } static class WordDiff extends DiffUtil.ItemCallback<Word> { @Override public boolean areItemsTheSame(@NonNull Word oldItem, @NonNull Word newItem) { return oldItem == newItem; } @Override public boolean areContentsTheSame(@NonNull Word oldItem, @NonNull Word newItem) { return oldItem.getWord().equals(newItem.getWord()); } } }
MAIN ACTIVITY
public class MainActivity extends AppCompatActivity { public static final int NEW_WORD_ACTIVITY_REQUEST_CODE = 1; private WordViewModel mWordViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); RecyclerView recyclerView = findViewById(R.id.recyclerview); final WordListAdapter adapter = new WordListAdapter(new WordListAdapter.WordDiff()); recyclerView.setAdapter(adapter); recyclerView.setLayoutManager(new LinearLayoutManager(this)); mWordViewModel = new ViewModelProvider(this).get(WordViewModel.class); mWordViewModel.getAllWords().observe(this, new Observer<List<Word>>() { @Override public void onChanged(List<Word> list) { adapter.submitList(list); } }); FloatingActionButton fab = findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(MainActivity.this, NewWordActivity.class); MainActivity.this.startActivityForResult(intent, NEW_WORD_ACTIVITY_REQUEST_CODE); } }); } public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == NEW_WORD_ACTIVITY_REQUEST_CODE && resultCode == RESULT_OK) { Word word = new Word(data.getStringExtra(NewWordActivity.EXTRA_REPLY)); mWordViewModel.insert(word); } else { Toast.makeText( getApplicationContext(), R.string.empty_not_saved, Toast.LENGTH_LONG).show(); } } }
Solution
First of all, check generated by Room library code
@Override
public LiveData<List<Word>> getAlphabetizedWords() {
// ...
return new ComputableLiveData<List<Word>>() {
private Observer _observer;
@Override
protected List<Word> compute() {
if (_observer == null) {
_observer = new Observer("word_table") {
@Override
public void onInvalidated(@NonNull Set<String> tables) {
invalidate();
}
};
__db.getInvalidationTracker().addWeakObserver(_observer);
}
final Cursor cursor = __db.query(_statement);
final List<Word> _result = new ArrayList<Word>(cursor.getCount());
while (cursor.moveToNext()) {
final Word item = createNewWordInstanceFromCursorData(cursor);
_result.add(item);
}
// ...
}
}.getLiveData();
}
After the first execution of the getAlphabetizedWords()
method, the new table invalidation observer will be added. This Observer
will be triggered after each transaction in a database table, such as inserting a new word or deleting one. So, after this, invalidate()
method of ComputableLiveData
will be called, which leads to the recomputation of the entire list of the words. LiveData
value will be set to an entirely new instance of List
with new instances of Word
items.
In WordDiff
class areItemsTheSame()
method is implemented to check object references. But in this case, we will get new Word
instances and this implementation will return false. This leads to a full refresh of the list. In this case, you might change the implementation to the following
static class WordDiff extends DiffUtil.ItemCallback<Word> {
@Override
public boolean areItemsTheSame(@NonNull Word oldItem, @NonNull Word newItem) {
return oldItem.getWord().equals(newItem.getWord());
}
// ...
}
In more complex cases, you might check the unique data per elements, such as item id. See more: https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil.ItemCallback#areItemsTheSame(T,T)
Answered By - Ilya Pechuro
Answer Checked By - David Goodson (JavaFixing Volunteer)