notifyDataSetChanged не работает на RecyclerView

Я получаю данные с сервера, затем анализирую их и сохраняю в списке. Я использую этот список для адаптера RecyclerView. Я использую фрагменты.

Я использую Nexus 5 с KitKat. Я использую библиотеку поддержки для этого. Будет ли это иметь значение?

Вот мой код: (используя фиктивные данные для вопроса)

Переменные-члены:

List<Business> mBusinesses = new ArrayList<Business>();

RecyclerView recyclerView;
RecyclerView.LayoutManager mLayoutManager;
BusinessAdapter mBusinessAdapter;

мой onCreateView():

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {

    // Getting data from server
    getBusinessesDataFromServer();

    View view = inflater.inflate(R.layout.fragment_business_list,
            container, false);
    recyclerView = (RecyclerView) view
            .findViewById(R.id.business_recycler_view);
    recyclerView.setHasFixedSize(true);

    mLayoutManager = new LinearLayoutManager(getActivity());
    recyclerView.setLayoutManager(mLayoutManager);

    mBusinessAdapter = new BusinessAdapter(mBusinesses);
    recyclerView.setAdapter(mBusinessAdapter);

    return view;
}

После получения данных с сервера, parseResponse() называется.

protected void parseResponse(JSONArray response, String url) {
    // insert dummy data for demo

    mBusinesses.clear();

    Business business;

    business = new Business();
    business.setName("Google");
    business.setDescription("Google HeadQuaters");
    mBusinesses.add(business);

    business = new Business();
    business.setName("Yahoo");
    business.setDescription("Yahoo HeadQuaters");
    mBusinesses.add(business);

    business = new Business();
    business.setName("Microsoft");
    business.setDescription("Microsoft HeadQuaters");
    mBusinesses.add(business);

    Log.d(Const.DEBUG, "Dummy Data Inserted\nBusinesses Length: "
            + mBusinesses.size());

    mBusinessAdapter = new BusinessAdapter(mBusinesses);
    mBusinessAdapter.notifyDataSetChanged();
}

Мой BusinessAdapter:

public class BusinessAdapter extends
    RecyclerView.Adapter<BusinessAdapter.ViewHolder> {

    private List<Business> mBusinesses = new ArrayList<Business>();

    // Provide a reference to the type of views that you are using
    // (custom viewholder)
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView mTextViewName;
        public TextView mTextViewDescription;
        public ImageView mImageViewLogo;

        public ViewHolder(View v) {
            super(v);
            mTextViewName = (TextView) v
                    .findViewById(R.id.textView_company_name);
            mTextViewDescription = (TextView) v
                    .findViewById(R.id.textView_company_description);
            mImageViewLogo = (ImageView) v
                    .findViewById(R.id.imageView_company_logo);
        }
    }

    // Provide a suitable constructor (depends on the kind of dataset)
    public BusinessAdapter(List<Business> myBusinesses) {

        Log.d(Const.DEBUG, "BusinessAdapter -> constructor");

        mBusinesses = myBusinesses;
    }

    // Create new views (invoked by the layout manager)
    @Override
    public BusinessAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
            int viewType) {

        Log.d(Const.DEBUG, "BusinessAdapter -> onCreateViewHolder()");

        // create a new view
        View v = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.item_business_list, parent, false);

        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        // - get element from your dataset at this position
        // - replace the contents of the view with that element

        Log.d(Const.DEBUG, "BusinessAdapter -> onBindViewHolder()");

        Business item = mBusinesses.get(position);
        holder.mTextViewName.setText(item.getName());
        holder.mTextViewDescription.setText(item.getDescription());
        holder.mImageViewLogo.setImageResource(R.drawable.ic_launcher);

    }

    // Return the size of your dataset (invoked by the layout manager)
    @Override
    public int getItemCount() {

        Log.d(Const.DEBUG, "BusinessAdapter -> getItemCount()");

        if (mBusinesses != null) {
            Log.d(Const.DEBUG, "mBusinesses Count: " + mBusinesses.size());
            return mBusinesses.size();
        }
        return 0;
    }
}

Но я не получаю данные, отображаемые в представлении. Что я делаю неправильно?

Вот мой журнал,

07-14 21:15:35.669: D/xxx(2259): Dummy Data Inserted
07-14 21:15:35.669: D/xxx(2259): Businesses Length: 3
07-14 21:26:26.969: D/xxx(2732): BusinessAdapter -> constructor

Я не получаю никаких журналов после этого. не должны getItemCount() в адаптер надо вызывать снова?

9 ответов

Решение

В вашем parseResponse() вы создаете новый экземпляр BusinessAdapter класс, но вы на самом деле не используете его нигде, так что ваш RecyclerView не знает, что новый экземпляр существует.

Вам либо нужно:

  • Вызов recyclerView.setAdapter(mBusinessAdapter) снова, чтобы обновить ссылку на адаптер RecyclerView, чтобы указать на ваш новый
  • Или просто удалить mBusinessAdapter = new BusinessAdapter(mBusinesses); продолжить использовать существующий адаптер. Так как вы не изменили mBusinesses ссылка, адаптер будет по-прежнему использовать этот список массивов и должен корректно обновляться при вызове notifyDataSetChanged(),

Попробуйте этот метод:

List<Business> mBusinesses2 = mBusinesses;
mBusinesses.clear();
mBusinesses.addAll(mBusinesses2);
//and do the notification

немного времени, но это должно работать.

Просто чтобы дополнить другие ответы, так как я не думаю, что кто-то упомянул это здесь: notifyDataSetChanged() должен выполняться в главном потоке (другой notify<Something> методы RecyclerView.Adapter ну и конечно)

Из того, что я понимаю, так как у вас есть процедуры синтаксического анализа и призыв к notifyDataSetChanged() в том же блоке вы либо вызываете его из рабочего потока, либо выполняете синтаксический анализ JSON в основном потоке (что, как я уверен, вы также знаете как нет-нет). Таким образом, правильный путь будет:

protected void parseResponse(JSONArray response, String url) {
    // insert dummy data for demo
    // <yadda yadda yadda>
    mBusinessAdapter = new BusinessAdapter(mBusinesses);
    // or just use recyclerView.post() or [Fragment]getView().post()
    // instead, but make sure views haven't been destroyed while you were
    // parsing
    new Handler(Looper.getMainLooper()).post(new Runnable() {
        public void run() {
            mBusinessAdapter.notifyDataSetChanged();
        }
    });

}

PS Странная вещь, я не думаю, что вы получаете какие-либо указания на основной поток из IDE или журналов времени выполнения. Это только из моих личных наблюдений: если я позвоню notifyDataSetChanged() из рабочего потока я не получаю обязательного. Только исходный поток, создавший иерархию представлений, может касаться своего сообщения представлений или чего-либо подобного - он просто молча завершается неудачей (и в моем случае один вызов вне основного потока может даже предотвратить успешное выполнение вызовов основного потока, возможно, из-за какого-либо состояния гонки)

Более того, ни в справочнике по API RecyclerView.Adapter, ни в соответствующем официальном руководстве разработчика явно не упоминаются основные требования к потокам на данный момент (на данный момент 2017), и ни одно из правил проверки линтов в Android Studio, похоже, также не касается этой проблемы.

Но вот объяснение этого самого автора

У меня была такая же проблема. Я просто решил это с объявлением adapter перед публикой onCreate класса.

PostAdapter postAdapter;

после этого

postAdapter = new PostAdapter(getActivity(), posts);
recList.setAdapter(postAdapter);

наконец я позвонил:

@Override
protected void onPostExecute(Void aVoid) {
    super.onPostExecute(aVoid);
    // Display the size of your ArrayList
    Log.i("TAG", "Size : " + posts.size());
    progressBar.setVisibility(View.GONE);
    postAdapter.notifyDataSetChanged();
}

Пусть это поможет вам.

Хотя это немного странно, но notifyDataSetChangedне работает без установки новых значений для адаптера. Итак, вам следует сделать:

array = getNewItems();                    
((MyAdapter) mAdapter).setValues(array);  // pass the new list to adapter !!!
mAdapter.notifyDataSetChanged();       

Это сработало для меня.

В моем случае принудительный запуск #notifyDataSetChanged в основном потоке пользовательского интерфейса исправит

public void refresh() {
        clearSelection();
        // notifyDataSetChanged must run in main ui thread, if run in not ui thread, it will not update until manually scroll recyclerview
        ((Activity) ctx).runOnUiThread(new Runnable() {
            @Override
            public void run() {
                adapter.notifyDataSetChanged();
            }
        });
    }

Удалите старую модель представления и установите новые данные для адаптера и вызовите notifyDataSetChanged()

У меня всегда есть эта проблема: я забываю, что RecyclerView ожидает новый экземпляр List каждый раз, когда вы загружаете адаптер.

      List<X> deReferenced = new ArrayList(myList);
adapter.submitList(deReferenced);

Наличие «того же» списка (ссылки) означает не объявлять «новый», даже если размер списка изменяется, потому что изменения, внесенные в список, также распространяются на другие списки (когда они просто объявляются как this.localOtherList = myList) акцент на ключевом слове " = ", обычно компоненты, сравнивающие коллекции, делают копию результата постфактум и сохраняют его как "старый", но не Android DiffUtil.

Итак, если ваш компонент выдает один и тот же список каждый раз, когда вы его отправляете, RecyclerView не запускает новый проход макета. Причина в том, что ... AFAIR, прежде чем DiffUtil даже попытается применить алгоритм Майерса, есть строка, выполняющая:

       if (newList == mList)) {return;}

Я не уверен, насколько «хорошая практика» разыменования в той же системе фактически определяется как «хорошая» ... Тем более, что ожидается, что алгоритм сравнения будет иметь новый (пересмотренный) и старый (исходный) компонент, который ДОЛЖЕН теоретически разыменовать коллекцию после завершения процесса, но ... кто знает ...?

Но подождите, есть еще кое-что ...

выполнение new ArrayList() разыменовывает список, НО по какой-то причине Oracle решила, что они должны создать второй «ArrayList» с тем же именем, но с другой функциональностью.

Этот ArrayList находится в классе Arrays.

      /**
     * Returns a fixed-size list backed by the specified array.  (Changes to
     * the returned list "write through" to the array.)  This method acts
     * as bridge between array-based and collection-based APIs, in
     * combination with {@link Collection#toArray}.  The returned list is
     * serializable and implements {@link RandomAccess}.
     *
     * <p>This method also provides a convenient way to create a fixed-size
     * list initialized to contain several elements:
     * <pre>
     *     List&lt;String&gt; stooges = Arrays.asList("Larry", "Moe", "Curly");
     * </pre>
     *
     * @param <T> the class of the objects in the array
     * @param a the array by which the list will be backed
     * @return a list view of the specified array
     */
    @SafeVarargs
    @SuppressWarnings("varargs")
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a); //Here
    }

Эта запись забавна, потому что если вы:

      Integer[] localInts = new Integer[]{1, 2, 8};
Consumer<List<Integer>> intObserver;

public void getInts(Consumer<List<Integer>> intObserver) {
    this.intObserver = intObserver;
    dispatch();
}

private void dispatch() {
    List<Integer> myIntegers = Arrays.asList(localInts);
    intObserver.accept(myIntegers);
}
    

... потом:

      getInts(
    myInts -> {
    adapter.submitList(myInts); //myInts = [1, 2, 8]
    }
);
    

Мало того, что отправленный список подчиняется разыменованию при каждой отправке, но и когда localInts переменная изменена,

      public void set(int index, Integer value) {
    localInts[index] = value;
    dispatch(); // dispatch again
}

...

      myModel.set(1, 4) // localInts = [1, 4, 8]

это изменение также передается в список ВНУТРИ RecyclerView, это означает, что при следующей отправке (newList == mList)возвратит «ложь» , разрешающее Diffutils для запуска алгоритма Майера, НОareContentsTheSame(@NonNull T oldItem, @NonNull T newItem) обратный звонок от ItemCallback<T> interface выдает значение «true» при достижении индекса 1. в основном, говоря, что «индекс 1 внутри RecyclerView(который должен был быть 2 в предыдущей версии) всегда был 4», и проход макета по-прежнему не выполняется.

Итак, путь в этом случае следующий:

      List<Integer> trulyDereferenced = new ArrayList<>(Arrays.asList(localInts));
adapter.submitList(trulyDereferenced);

Решение для тех пользователей, которые используют обратную раскладку, например список чатов.

добавьте эту функцию в свой класс адаптера

      fun updateChatList(msgData: DataDetails) { // DataDetails is custom model class
    var tmpdata = ArrayList<DataDetails>()
    tmpdata.add(msgData)
    tmpdata.addAll(data)  //data is your data define in adapter
    data.clear()
    data.addAll(tmpdata)
    this.notifyDataSetChanged()
}
Другие вопросы по тегам