Проблема с использованием LoaderManager.LoaderCallbacks <Cursor>с пользовательским RecyclerView.Adapter - PullRequest
0 голосов
/ 10 ноября 2018

Я попытался загрузить список, используя ListView вместе с LoaderManager.LoaderCallbacks и пользовательским CursorAdapter, и он работает нормально.Но я пытаюсь сделать то же самое, используя RecyclerView вместе с пользовательским RecyclerView.Adapter, но у меня возникает эта проблема:

Я получаю список, отображаемый в первый раз, но когда я поворачиваю устройство, список исчезает.

enter image description here

enter image description here

Вот код, пожалуйста, посмотрите.

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();
        }
    }
}

Я не могу выяснить проблему extact.Пожалуйста, помогите.

Ответы [ 2 ]

0 голосов
/ 11 ноября 2018

Вкратце, проблема здесь в том, что после ротации вам вручают те же Cursor, которые вы ранее зациклили перед вращением, но вы не учитываете его текущую позицию.

A Cursor отслеживает и сохраняет свою позицию в своем наборе записей, так как я уверен, что вы собрали из различных move*() методов, которые он содержит. При первом создании позиция Cursor будет установлена ​​прямо перед первой записью; то есть его позиция будет установлена ​​на -1.

Когда вы впервые запускаете приложение, LoaderManager вызывает onCreateLoader(), где создается экземпляр CursorLoader, а затем заставляет его загрузить и доставить Cursor с позицией Cursor в -1. В этот момент цикл while (cursor.moveToNext()) работает так же, как и ожидалось, поскольку первый вызов moveToNext() переместит его в первую позицию (индекс 0), а затем в каждую доступную позицию после этого, до конца.

Однако при повороте LoaderManager определяет, что у него уже есть запрошенный Loader (определяется по ID), который сам видит, что в него уже загружен соответствующий Cursor, поэтому он просто сразу же доставляет тот же Cursor объект снова. (Это главная особенность Loader фреймворка - он не будет перезагружать ресурсы, которые у него уже есть, независимо от изменений конфигурации.) В этом суть проблемы. Это Cursor было оставлено в последней позиции, в которую оно было перемещено перед вращением; то есть в его конце. Следовательно, Cursor не может moveToNext(), так что цикл while просто никогда не запускается вообще, после начальной onLoadFinished(), до вращения.

Самым простым исправлением при заданной настройке было бы вручную изменить положение Cursor самостоятельно. Например, в getItems() измените if на moveToFirst(), если Cursor не равно нулю, и измените while на do-while, чтобы мы случайно не пропустили первую запись. То есть:

if (cursor != null && cursor.moveToFirst()) { 
    do {
        int columnIndexId = cursor.getColumnIndex(ItemEntry._ID);
        ...
    } while (cursor.moveToNext());
}

При этом, когда тот же самый Cursor объект повторно доставляется, его позиция как бы «сбрасывается» в положение 0. Так как эта позиция находится непосредственно в первой записи, а не прямо перед ней (помните, изначально -1), мы изменяем на do-while, так что первый вызов moveToNext() не пропускает первую запись в Cursor.


Примечания:

  • Я бы упомянул, что можно реализовать RecyclerView.Adapter для непосредственного получения Cursor, аналогично старому CursorAdapter. В этом случае Cursor обязательно будет перемещен в методе onBindViewHolder() в правильную позицию для каждого элемента, а отдельный ArrayList будет ненужным. Это займет немного усилий, но перевод CursorAdapter в RecyclerView.Adapter не очень сложен. В качестве альтернативы, конечно, уже есть решения. (Например, возможно, этот , хотя я не могу за это ручаться, я часто вижу доверенного друга, который часто его рекомендует).

  • Я бы также упомянул, что нативный Loader фреймворк устарел, в пользу более нового ViewModel / LiveData фреймворка архитектуры в библиотеках поддержки . Однако, похоже, что новейшая библиотека androidx имеет свою собственную улучшенную структуру Loader, которая представляет собой простую оболочку для указанной установки ViewModel / LiveData. Это, кажется, хороший и простой способ использовать известные конструкции Loader, но при этом использовать преимущества последних усовершенствований архитектуры.

0 голосов
/ 10 ноября 2018

Вместо LoaderManager.initLoader() звоните LoaderManager.restartLoader()

...