I have tried loading the list using the ListView along with LoaderManager.LoaderCallbacks and custom CursorAdapter and it works fine. But I am trying to accomplish the same using RecyclerView along with custom RecyclerView.Adapter but I am getting this issue:
I am getting the list displayed for the first time but when I rotate the device the list disappears.
Here is the code, please have a look.
CatalogActivity
public class CatalogActivity extends AppCompatActivity implements ItemAdapter.OnItemClickListener,
LoaderManager.LoaderCallbacks<Cursor> {
private static final int ITEMS_LOADER_ID = 1;
public static final String EXTRA_ITEM_NAME = "extra_item_name";
public static final String EXTRA_ITEM_STOCK = "extra_item_stock";
@BindView(R.id.list_items)
RecyclerView mListItems;
private ItemAdapter mItemAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_catalog);
ButterKnife.bind(this);
setupListItems();
getLoaderManager().initLoader(ITEMS_LOADER_ID, null, this);
}
private void setupListItems() {
mListItems.setHasFixedSize(true);
LayoutManager layoutManager = new LinearLayoutManager(this);
mListItems.setLayoutManager(layoutManager);
mListItems.setItemAnimator(new DefaultItemAnimator());
mListItems.addItemDecoration(new DividerItemDecoration(this, LinearLayout.VERTICAL));
mItemAdapter = new ItemAdapter(getApplicationContext(), this);
mListItems.setAdapter(mItemAdapter);
}
@Override
public void OnClickItem(int position) {
Intent intent = new Intent(this, EditorActivity.class);
Item item = mItemAdapter.getItems().get(position);
intent.putExtra(EXTRA_ITEM_NAME, item.getName());
intent.putExtra(EXTRA_ITEM_STOCK, item.getStock());
startActivity(intent);
}
private ArrayList<Item> getItems(Cursor cursor) {
ArrayList<Item> items = new ArrayList<>();
if (cursor != null) {
while (cursor.moveToNext()) {
int columnIndexId = cursor.getColumnIndex(ItemEntry._ID);
int columnIndexName = cursor.getColumnIndex(ItemEntry.COLUMN_NAME);
int columnIndexStock = cursor.getColumnIndex(ItemEntry.COLUMN_STOCK);
int id = cursor.getInt(columnIndexId);
String name = cursor.getString(columnIndexName);
int stock = Integer.parseInt(cursor.getString(columnIndexStock));
items.add(new Item(id, name, stock));
}
}
return items;
}
@Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle bundle) {
switch (loaderId) {
case ITEMS_LOADER_ID: {
String[] projection = {
ItemEntry._ID,
ItemEntry.COLUMN_NAME,
ItemEntry.COLUMN_STOCK
};
return new CursorLoader(
this,
ItemEntry.CONTENT_URI,
projection,
null,
null,
null
);
}
default:
return null;
}
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
mItemAdapter.setItems(getItems(cursor));
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
}
}
ItemAdapter
public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ItemViewHolder> {
private ArrayList<Item> mItems;
private OnItemClickListener mOnItemClickListener;
private Context mContext;
public ItemAdapter(Context context, OnItemClickListener onItemClickListener) {
mOnItemClickListener = onItemClickListener;
mContext = context;
}
public void setItems(ArrayList<Item> items) {
if (items != null) {
mItems = items;
notifyDataSetChanged();
}
}
public ArrayList<Item> getItems() {
return mItems;
}
public interface OnItemClickListener {
void OnClickItem(int position);
}
public class ItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
@BindView(R.id.tv_item)
TextView tv_item;
@BindView(R.id.tv_stock)
TextView tv_stock;
public ItemViewHolder(@NonNull View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
int position = getAdapterPosition();
mOnItemClickListener.OnClickItem(position);
}
}
@NonNull
@Override
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int i) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_inventory, parent, false);
return new ItemViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull ItemViewHolder itemViewHolder, int position) {
final Item item = mItems.get(position);
itemViewHolder.tv_item.setText(item.getName());
itemViewHolder.tv_stock.setText(mContext.getString(R.string.display_stock, item.getStock()));
}
@Override
public int getItemCount() {
if (mItems == null) {
return 0;
} else {
return mItems.size();
}
}
}
I am not able to figure out the extact issue. Please help.


Briefly, the issue here is that, after rotation, you're being handed the same
Cursorthat you had previously looped over before the rotation, but you're not accounting for its current position.A
Cursortracks and maintains its own position within its set of records, as I'm sure you've gathered from the variousmove*()methods it contains. When first created, aCursor's position will be set to right before the first record; i.e., its position will be set to-1.When you first start your app, the
LoaderManagercallsonCreateLoader(), where yourCursorLoaderis instantiated, and then causes it to load and deliver itsCursor, with theCursor's position at-1. At this point, thewhile (cursor.moveToNext())loop works just as expected, since the firstmoveToNext()call will move it to the first position (index0), and then to each available position after that, until the end.Upon rotation, however, the
LoaderManagerdetermines that it already has the requestedLoader(determined by ID), which itself sees that it already has the appropriateCursorloaded, so it just immediately delivers that sameCursorobject again. (This is a major feature of theLoaderframework – it won't reload resources it already has, regardless of configuration changes.) This is the crux of the issue. ThatCursorhas been left at the last position to which it was moved before the rotation; i.e., at its end. Consequently, theCursorcannotmoveToNext(), so thatwhileloop just never runs at all, after the initialonLoadFinished(), before rotation.The simplest fix, with the given setup, would be to manually reposition the
Cursoryourself. For example, ingetItems(), change theiftomoveToFirst()if theCursoris not null, and change thewhileto ado-while, so we don't inadvertently skip over the first record. That is:With this, when that same
Cursorobject is re-delivered, its position is kinda "reset" to position0. Since that position is directly on the first record, rather than right before it (remember, initially-1), we change to ado-while, so that the firstmoveToNext()call doesn't skip the first record in theCursor.Notes:
I would mention that it is possible to implement a
RecyclerView.Adapterto take aCursordirectly, similar to the oldCursorAdapter. In this, theCursorwould necessarily be moved in theonBindViewHolder()method to the correct position for each item, and the separateArrayListwould be unnecessary. It'd take a little effort, but translatingCursorAdapterto aRecyclerView.Adapterisn't terribly difficult. Alternatively, there are certainly solutions already available. (For example, possibly, this one, though I cannot vouch for it, atm, I often see a trusted fellow user recommend it often.)I would also mention that the native
Loaderframework has been deprecated, in favor of the newerViewModel/LiveDataarchitecture framework in support libraries. However, it appears that the newest androidx library has its own internal, improvedLoaderframework which is a simple wrapper around saidViewModel/LiveDatasetup. This seems to be a nice, easy way to utilize the knownLoaderconstructs while still benefiting from the recent architecture refinements.