Realm Adapter с отсортированными результатами Realm ведет себя некорректно
Я недавно застрял с проблемой. Проблема заключается в том, что при использовании отсортированного RealmResults в качестве источника для RealmRecyclerViewAdapter, если добавлено более 2 элементов, некоторые из предыдущих (средние элементы) перезаписываются первым добавленным элементом. Я знаю, что это звучит неясно, и я очень смущен, пытался разобраться в этом в течение нескольких дней, но пока не нашел решения или даже объяснения.
Код инициализации
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
mAttachmentDataSource = realm.where(Attachment.class).equalTo("parentId", presenter.getId()).findAllSorted("order", Sort.DESCENDING);
mAttachmentAdapter = new AttachmentAdapter(getActivity(), mAttachmentDataSource);
mAttachmentListManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false);
mAttachmentListView.setLayoutManager(mAttachmentListManager);
mAttachmentListView.setAdapter(mAttachmentAdapter);
...
}
Код адаптера:
public class AttachmentAdapter extends RealmRecyclerViewAdapter<Attachment, AttachmentAdapter.ViewHolder> {
private Context mContext;
public boolean mReadOnly;
private int limit;
public void setLimit(int limit) {
this.limit = limit;
}
public AttachmentAdapter(Context context, @NonNull OrderedRealmCollection<Attachment> attachments) {
super(attachments, true);
initialize(context, false);
}
public AttachmentAdapter(Context context, @NonNull OrderedRealmCollection<Attachment> attachments, boolean readonly) {
super(attachments, true);
initialize(context, readonly);
}
private void initialize(Context context, boolean readonly) {
setHasStableIds(true);
mContext = context;
mReadOnly = readonly;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
// Inflate the custom layout
View contactView = inflater.inflate(R.layout.item_attachment, parent, false);
// Return a new holder instance
return new ViewHolder(contactView);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (getData() == null) return;
holder.mThumbFileIconImageView.setVisibility(View.GONE);
if (position == 0 && !mReadOnly) {
holder.mRemoveIconView.setVisibility(View.GONE);
holder.mThumbnailView.setBackground(ActivityCompat.getDrawable(mContext,
R.drawable.dashed_accented_no_border));
RxUtils.clicks(holder.mThumbnailView)
.subscribe(v -> openAttachmentSelection());
holder.itemView.setVisibility(limit == getItemCount() - 1 ? View.GONE : View.VISIBLE);
} else {
Attachment model = getData().get(position);
long id = model.getId();
holder.initialize(id, model.getParentId());
holder.mRemoveIconView.setVisibility(mReadOnly ? View.GONE : View.VISIBLE);
holder.mRemoveIconView.setTag(id);
MediaType mediaType = MediaType.parse(model.getMimeType());
String path = model.getAbsoluteFilePath();
if (mediaType != null && mediaType.is(MediaType.ANY_IMAGE_TYPE)) {
if (path != null) {
Glide.with(mContext)
.fromString()
.load(path)
.listener(new RequestListener<String, GlideDrawable>() {
@Override
public boolean onException(Exception e, String s, Target<GlideDrawable> target, boolean b) {
holder.mProgressBar.setVisibility(View.GONE);
return false;
}
@Override
public boolean onResourceReady(GlideDrawable glideDrawable, String s, Target<GlideDrawable> target, boolean b, boolean b1) {
holder.mProgressBar.setVisibility(View.VISIBLE);
return false;
}
})
.centerCrop()
.crossFade()
.thumbnail(0.1f)
.into(holder.mThumbnailView);
} else if (model.data != null) {
Glide.with(mContext)
.fromBytes()
.load(model.data)
.listener(new RequestListener<byte[], GlideDrawable>() {
@Override
public boolean onException(Exception e, byte[] bytes, Target<GlideDrawable> target, boolean b) {
holder.mProgressBar.setVisibility(View.GONE);
return false;
}
@Override
public boolean onResourceReady(GlideDrawable glideDrawable, byte[] bytes, Target<GlideDrawable> target, boolean b, boolean b1) {
holder.mProgressBar.setVisibility(View.VISIBLE);
return false;
}
})
.centerCrop()
.crossFade()
.thumbnail(0.1f)
.into(holder.mThumbnailView);
}
} else {
int icon = AttachmentUtils.getAttachmentIcon(mediaType);
holder.mThumbFileIconImageView.setVisibility(View.VISIBLE);
holder.mThumbFileIconImageView.setImageResource(icon);
}
holder.mOverlayView.setVisibility(View.INVISIBLE);
holder.mThumbnailView.setOnClickListener(e -> holder.openGallery(mContext));
holder.mRemoveIconView.setOnClickListener(e -> holder.remove());
}
}
@Override
public int getItemCount() {
return getData().size();
}
public void openAttachmentSelection() {
new MaterialDialog.Builder(mContext)
.title(mContext.getText(R.string.selectAttachment))
.backgroundColorRes(R.color.white)
.contentColorRes(R.color.black)
.titleColorRes(R.color.black)
.items(R.array.select_attachment_types)
.itemsCallbackSingleChoice(-1, (dialog, view, which, text) -> {
switch (which) {
case 0:
CameraUtils.returnPicture((AppCompatActivity) mContext);
break;
case 1:
getAttachmentImageFile();
break;
case 2:
getAttachmentFile();
break;
}
return true;
})
.show();
}
private void getAttachmentImageFile() {
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.MEDIA_CONTENT_CONTROL) == PackageManager.PERMISSION_GRANTED) {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI);
intent.setType("image/*");
intent.putExtra("return-data", true);
((AppCompatActivity) mContext).startActivityForResult(intent, RequestCodes.REQUEST_IMAGE_SELECT);
return;
}
ActivityCompat.requestPermissions(((AppCompatActivity) mContext),
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
RequestCodes.REQUEST_PERMISSION_IMAGE
);
}
private void getAttachmentFile() {
// Create the ACTION_GET_CONTENT Intent
Intent getContentIntent = FileUtils.createGetContentIntent();
Intent intent = Intent.createChooser(getContentIntent, mContext.getString(R.string.attachment_list_select_file));
((AppCompatActivity) mContext).startActivityForResult(intent, RequestCodes.RESULT_FILE_SELECTED);
}
public class ViewHolder extends RecyclerView.ViewHolder {
private AttachmentPresenter presenter;
@BindView(R.id.item_attachment_thumbnail_overlay)
AppCompatImageView mOverlayView;
@BindView(R.id.item_attachment_thumbnail_layout)
FrameLayout mFrameLayout;
@BindView(R.id.item_attachment_thumbnail)
AppCompatImageView mThumbnailView;
@BindView(R.id.item_attachment_close_button)
AppCompatImageView mRemoveIconView;
@BindView(R.id.item_attachment_thumbnail_file_icon)
AppCompatImageView mThumbFileIconImageView;
@BindView(R.id.item_attachment_progress_bar)
ProgressBar mProgressBar;
public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
public void initialize(long id, long parentId) {
if(this.presenter == null) this.presenter = new AttachmentPresenter();
this.presenter.initialize(id, parentId);
}
public void openGallery(Context mContext) {
AppCompatActivity activity = ((AppCompatActivity) mContext);
Attachment attachment = this.presenter.getAttachment();
;
if (attachment == null || activity == null) return;
FragmentTransaction fragmentTransaction = activity.getSupportFragmentManager().beginTransaction();
Fragment fragment = activity.getSupportFragmentManager().findFragmentByTag(AttachmentDialogFragment.FRAGMENT_TAG);
if (fragment != null) {
fragmentTransaction.remove(fragment);
}
fragmentTransaction.addToBackStack(null);
// Create and show the dialog.
AttachmentDialogFragment newFragment = AttachmentDialogFragment.newInstance(
attachment.getParentId(),
attachment.getId()
);
newFragment.show(fragmentTransaction, AttachmentDialogFragment.FRAGMENT_TAG);
}
public void remove() {
this.presenter.remove();
this.presenter.unbind();
this.presenter = null;
}
}
}
Как добавляется вложение:
@Override
public void addAttachment(Bitmap image) {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
File file = ImageUtils.saveToFile(image, "attachment_" + mId + "_" + timeStamp, context);
Realm realm = Realm.getDefaultInstance();
String mimeType = FileUtils.getMimeType(file);
realm.executeTransactionAsync(t -> {
long id = getId();
Attachment attachment = Attachment.create(t, Opportunity.class, id);
attachment.setAbsoluteFilePath(file.getAbsolutePath());
attachment.setMimeType(mimeType);
t.copyToRealmOrUpdate(attachment);
});
realm.close();
}
@Override
public void addAttachment(Uri uri) {
File file;
String absolutePath = null;
String mimeType = FileUtils.getMimeType(context, uri);
String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
try {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
file = File.createTempFile(
"attachment_" + mId + "_" + timeStamp , /* prefix */
"." + extension, /* suffix */
context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) /* directory */
);
File tempFile = new File(ImageUtils.getPath(uri, context));
GeneralUtils.copy(tempFile, file);
absolutePath = file.getAbsolutePath();
} catch (IOException e) {
e.printStackTrace();
}
Realm realm = Realm.getDefaultInstance();
final String path = absolutePath;
realm.executeTransactionAsync(t -> {
long id = getId();
Attachment attachment = Attachment.create(t, Opportunity.class, id);
attachment.setAbsoluteFilePath(path);
attachment.setMimeType(mimeType);
t.copyToRealmOrUpdate(attachment);
for (Attachment a:t.where(Opportunity.class).findFirst().getAttachments()
) {
a.setCreatedAt(new Date());
}
});
realm.close();
}
Странное поведение, которое я испытываю, таково:
После добавления одной очаровательной картинки кота в качестве вложения:
После добавления другого (обратите внимание, что первый нажимается вправо, добавленный - перед кнопкой):
После добавления другой картинки первая картинка заменила предыдущую
Сначала я думал, что это неправильно перерабатывает, но не могу понять, что именно происходит. Странная вещь - если я вообще не использую упорядочивание и просто использую .findAll() вместо .findAllSorted("order", Sort.DESCENDING), все работает правильно, но добавленное изображение не помещается перед другими (что является моя цель).