У меня есть приложение Android, в котором пользователь может изменять несколько String
элементов, используя EditText
одновременно, поэтому мне нужно выяснить, что изменилось, чтобы уведомить сервер об изменениях (создать новый элемент, обновить существующий элемент или удалить последний), поэтому я использую DiffUtil
с ListUpdateCallback
для этого. Проблема в том, что если старый список имеет размер 2 элемента; и я удалил элемент с индексом 0, затем добавил 3 элемента в конец списка, я получаю обратный вызов к onInserted
с неправильным параметром позиции, который приводит к IndexOutOfBoundsException
, (и это поведение удаления любой элемент в старом списке, кроме последнего) , пожалуйста, посмотрите на этот GIF, который показывает проблему .
Я пробовал следующий код с другими изменениями, внесенными в новый список, такими как удаление по индексу 1, а затем добавление 3 элементов в конец списка, и все работает отлично!
Массив, с которым я работаю, имеет тип Answer, который является классом:
public class Answer {
private String id;
private String questionId;
private String text;
private Integer count;
}
Обратите внимание, что 2 разных объекта могут быть идентифицированы по идентификатору, если два элемента одинаковы; тогда содержимое может быть идентифицировано по тексту.
DiffUtil.Callback
public class AnswersDiffCallback extends DiffUtil.Callback {
List<Answer> newAnswers;
List<Answer> oldAnswers;
public AnswersDiffCallback(List<Answer> newAnswers, List<Answer> oldAnswers) {
this.newAnswers = newAnswers;
this.oldAnswers = oldAnswers;
}
@Override
public int getOldListSize() {
return oldAnswers == null ? 0 : oldAnswers.size();
}
@Override
public int getNewListSize() {
return newAnswers == null ? 0 : newAnswers.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
Answer oldAnswer = oldAnswers.get(oldItemPosition);
Answer newAnswer = newAnswers.get(newItemPosition);
return Objects.equals(oldAnswer.getId(), newAnswer.getId());
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
Answer oldAnswer = oldAnswers.get(oldItemPosition);
Answer newAnswer = newAnswers.get(newItemPosition);
return Objects.equals(oldAnswer.getText(), newAnswer.getText());
}
}
в ListUpdateCallback Я пытаюсь зарегистрировать обратные вызовы, которые я получаю, чтобы проверить, работает ли он, прежде чем я говорю с сервером.
Log.d(TAG, "answers: oldAnswers = " + Utils.serializeObject(oldAnswers));
Log.d(TAG, "answers: newAnswers = " + Utils.serializeObject(newAnswers));
Log.d(TAG, "-----------------------------------------------------------------------------");
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new AnswersDiffCallback(newAnswers, oldAnswers), true);
diffResult.dispatchUpdatesTo(new ListUpdateCallback() {
@Override
public void onInserted(int position, int count) {
try {
Log.d(TAG, String.format("onInserted: (position, count) = (%d, %d)", position, count));
for (int i = position; i < position + count; i++) {
Log.d(TAG, "onInserted: newAnswer.text = " + newAnswers.get(i).getText());
}
} catch (Exception ex) {
Log.e(TAG, "onInserted: Exception", ex);
}
Log.d(TAG, "-----------------------------------------------------------------------------");
}
@Override
public void onRemoved(int position, int count) {
try {
Log.d(TAG, "onRemoved: (position, count) = (" + position + ", " + count + ")");
for (int i = position; i < position + count; i++) {
Log.d(TAG, String.format("onRemoved: (oldAnswer.id, oldAnswer.text) = (%s, %s)", oldAnswers.get(i).getId(), oldAnswers.get(i).getText()));
}
} catch (Exception ex) {
Log.e(TAG, "onRemoved: Exception", ex);
}
Log.d(TAG, "-----------------------------------------------------------------------------");
}
@Override
public void onMoved(int fromPosition, int toPosition) {
try {
Log.d(TAG, "onMoved: (fromPosition, toPosition) = (" + fromPosition + ", " + toPosition + ")");
Log.d(TAG, String.format("onMoved: (oldAnswer.id, oldAnswer.text) = (%s, %s)", oldAnswers.get(fromPosition).getId(), oldAnswers.get(fromPosition).getText()));
Log.d(TAG, String.format("onMoved: (newAnswer.id, newAnswer.text) = (%s, %s)", newAnswers.get(toPosition).getId(), newAnswers.get(toPosition).getText()));
} catch (Exception ex) {
Log.e(TAG, "onMoved: Exception", ex);
}
Log.d(TAG, "-----------------------------------------------------------------------------");
}
@Override
public void onChanged(int position, int count, @Nullable Object payload) {
try {
Log.d(TAG, "onChanged: (position, count) = (" + position + ", " + count + ")");
for (int i = position; i < position + count; i++) {
Log.d(TAG, String.format("onChanged: (oldAnswer.id, oldAnswer.text) = (%s, %s)", oldAnswers.get(i).getId(), oldAnswers.get(i).getText()));
Log.d(TAG, String.format("onChanged: (newAnswer.id, newAnswer.text) = (%s, %s)", newAnswers.get(i).getId(), newAnswers.get(i).getText()));
}
} catch (Exception ex) {
Log.e(TAG, "onChanged: Exception", ex);
}
Log.d(TAG, "-----------------------------------------------------------------------------");
}
});
и вот logcat за исключением того, что я получил:
2019-06-19 16:32:00.461 24515-24515/com.example.myApp D/AdminQuestionsFragment: answers: oldAnswers = [{"count":0,"id":"5d09a1236969e249cca42e96","questionId":"5d09a1236969e249cca42e95","text":"old answer 0"},{"count":0,"id":"5d09a1236969e249cca42e97","questionId":"5d09a1236969e249cca42e95","text":"old answer 1"}]
2019-06-19 16:32:00.501 24515-24515/com.example.myApp D/AdminQuestionsFragment: answers: newAnswers = [{"count":0,"id":"5d09a1236969e249cca42e97","questionId":"5d09a1236969e249cca42e95","text":"old answer 1"},{"text":"new answer 0"},{"text":"new answer 1"},{"text":"new answer 2"}]
2019-06-19 16:32:00.501 24515-24515/com.example.myApp D/AdminQuestionsFragment: -----------------------------------------------------------------------------
2019-06-19 16:35:19.550 24515-24515/com.example.myApp D/AdminQuestionsFragment: onInserted: (position, count) = (2, 3)
2019-06-19 16:35:19.550 24515-24515/com.example.myApp D/AdminQuestionsFragment: onInserted: newAnswer.text = new answer 1
2019-06-19 16:35:19.550 24515-24515/com.example.myApp D/AdminQuestionsFragment: onInserted: newAnswer.text = new answer 2
2019-06-19 16:35:19.599 24515-24515/com.example.myApp E/AdminQuestionsFragment: onInserted: Exception
java.lang.IndexOutOfBoundsException: Index: 4, Size: 4
at java.util.ArrayList.get(ArrayList.java:437)
at com.example.myApp.views.AdminQuestionsFragment$5.onInserted(AdminQuestionsFragment.java:333)
at androidx.recyclerview.widget.BatchingListUpdateCallback.dispatchLastEvent(BatchingListUpdateCallback.java:61)
at androidx.recyclerview.widget.BatchingListUpdateCallback.onRemoved(BatchingListUpdateCallback.java:96)
at androidx.recyclerview.widget.DiffUtil$DiffResult.dispatchRemovals(DiffUtil.java:921)
at androidx.recyclerview.widget.DiffUtil$DiffResult.dispatchUpdatesTo(DiffUtil.java:836)
at com.example.myApp.views.AdminQuestionsFragment.lambda$onActivityResult$8$AdminQuestionsFragment(AdminQuestionsFragment.java:320)
at com.example.myApp.views.-$$Lambda$AdminQuestionsFragment$KZmQo8gdnjCYX1JsaACEVkjSd1s.onChanged(Unknown Source:8)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:113)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:126)
at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:424)
at androidx.lifecycle.LiveData$LifecycleBoundObserver.onStateChanged(LiveData.java:376)
at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:361)
at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:188)
at androidx.lifecycle.LiveData.observe(LiveData.java:185)
at com.example.myApp.views.AdminQuestionsFragment.onActivityResult(AdminQuestionsFragment.java:387)
at androidx.fragment.app.FragmentActivity.onActivityResult(FragmentActivity.java:170)
at android.app.Activity.dispatchActivityResult(Activity.java:7454)
at android.app.ActivityThread.deliverResults(ActivityThread.java:4353)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4402)
at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:49)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
2019-06-19 16:35:19.599 24515-24515/com.example.myApp D/AdminQuestionsFragment: -----------------------------------------------------------------------------
2019-06-19 16:35:19.600 24515-24515/com.example.myApp D/AdminQuestionsFragment: onRemoved: (position, count) = (0, 1)
2019-06-19 16:35:19.603 24515-24515/com.example.myApp D/AdminQuestionsFragment: onRemoved: (oldAnswer.id, oldAnswer.text) = (5d09a1236969e249cca42e96, old answer 0)
2019-06-19 16:35:19.603 24515-24515/com.example.myApp D/AdminQuestionsFragment: -----------------------------------------------------------------------------
проблема с этой строкой:
2019-06-19 16:35:19.550 24515-24515/com.example.myApp D/AdminQuestionsFragment: onInserted: (position, count) = (2, 3)
почему позиция не 1?
Обновление 1
Вот logcat, если у меня есть старый список из 2 элементов, и я удалил элемент с индексом 1, а затем добавил 3 элемента в конец списка:
2019-06-19 18:25:24.368 28118-28118/com.example.myApp D/AdminQuestionsFragment: answers: oldAnswers = [{"count":0,"id":"5d09a1236969e249cca42e96","questionId":"5d09a1236969e249cca42e95","text":"old answer 0"},{"count":0,"id":"5d09a1236969e249cca42e97","questionId":"5d09a1236969e249cca42e95","text":"old answer 1"}]
2019-06-19 18:25:24.370 28118-28118/com.example.myApp D/AdminQuestionsFragment: answers: newAnswers = [{"count":0,"id":"5d09a1236969e249cca42e96","questionId":"5d09a1236969e249cca42e95","text":"old answer 0"},{"text":"new answer 0"},{"text":"new answer 1"},{"text":"new answer 2"}]
2019-06-19 18:25:24.370 28118-28118/com.example.myApp D/AdminQuestionsFragment: -----------------------------------------------------------------------------
2019-06-19 18:25:24.370 28118-28118/com.example.myApp D/AdminQuestionsFragment: onRemoved: (position, count) = (1, 1)
2019-06-19 18:25:24.371 28118-28118/com.example.myApp D/AdminQuestionsFragment: onRemoved: (oldAnswer.id, oldAnswer.text) = (5d09a1236969e249cca42e97, old answer 1)
2019-06-19 18:25:24.371 28118-28118/com.example.myApp D/AdminQuestionsFragment: -----------------------------------------------------------------------------
2019-06-19 18:25:24.372 28118-28118/com.example.myApp D/AdminQuestionsFragment: onInserted: (position, count) = (1, 3)
2019-06-19 18:25:24.372 28118-28118/com.example.myApp D/AdminQuestionsFragment: onInserted: newAnswer.text = new answer 0
2019-06-19 18:25:24.372 28118-28118/com.example.myApp D/AdminQuestionsFragment: onInserted: newAnswer.text = new answer 1
2019-06-19 18:25:24.372 28118-28118/com.example.myApp D/AdminQuestionsFragment: onInserted: newAnswer.text = new answer 2
2019-06-19 18:25:24.372 28118-28118/com.example.myApp D/AdminQuestionsFragment: -----------------------------------------------------------------------------