android - update - notifyDataSetChanged no funciona en RecyclerView
refresh recyclerview from viewholder (5)
Obtengo datos del servidor y luego los analizo y los almaceno en una Lista. Estoy usando esta lista para el adaptador RecyclerView. Estoy usando fragmentos.
Estoy usando un Nexus 5 con KitKat. Estoy usando la biblioteca de soporte para esto. ¿Esto hará una diferencia?
Aquí está mi código: (usando datos ficticios para la pregunta)
Variables del miembro:
List<Business> mBusinesses = new ArrayList<Business>();
RecyclerView recyclerView;
RecyclerView.LayoutManager mLayoutManager;
BusinessAdapter mBusinessAdapter;
Mi 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;
}
Después de obtener datos del servidor, se llama a 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();
}
Mi 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;
}
}
Pero no consigo los datos mostrados en la vista. ¿Qué estoy haciendo mal?
Aquí está mi registro,
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
No consigo ningún registro después de esto. ¿No debería llamarse de nuevo a getItemCount()
en el adaptador?
Borre su antiguo modelo de vista y configure los nuevos datos en el adaptador y llame a notifyDataSetChanged()
En su parseResponse()
está creando una nueva instancia de la clase BusinessAdapter
, pero en realidad no la está utilizando en ningún lugar, por lo que su RecyclerView
no sabe que existe la nueva instancia.
O bien necesitas:
- Llame nuevamente a
recyclerView.setAdapter(mBusinessAdapter)
para actualizar la referencia del adaptador de RecyclerView para que apunte a la nueva - O simplemente elimine
mBusinessAdapter = new BusinessAdapter(mBusinesses);
Para continuar utilizando el adaptador existente. Como no ha cambiado la referencia demBusinesses
, el adaptador seguirá utilizando esa lista de arreglos y debería actualizarse correctamente cuando llame anotifyDataSetChanged()
.
Pruebe este método:
List<Business> mBusinesses2 = mBusinesses;
mBusinesses.clear();
mBusinesses.addAll(mBusinesses2);
//and do the notification
un poco de tiempo, pero debería funcionar.
Solo para complementar las otras respuestas, ya que no creo que nadie haya mencionado esto aquí: notifyDataSetChanged()
debe ejecutarse en el hilo principal (otros métodos de notify<Something>
de RecyclerView.Adapter
también, por supuesto)
De lo que recojo, ya que tiene los procedimientos de análisis y la llamada a notifyDataSetChanged()
en el mismo bloque, lo está llamando desde un subproceso de trabajo o está haciendo un análisis JSON en el hilo principal (que también es un no -No como estoy seguro que lo sabes. Así que la forma correcta sería:
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();
}
});
}
Lo extraño es que no creo que se obtengan indicaciones sobre el tema principal del subproceso desde el IDE o los registros de tiempo de ejecución. Esto es solo por mis observaciones personales: si llamo a notifyDataSetChanged()
desde un subproceso de trabajo, no obtengo lo obligatorio. Solo el subproceso original que creó una jerarquía de vistas puede tocar su mensaje de vistas o algo así, simplemente falla silenciosamente (y en mi caso, una llamada fuera del hilo principal puede incluso impedir que las llamadas al hilo principal funcionen correctamente, probablemente debido a algún tipo de condición de carrera)
Además, ni la referencia de la aplicación RecyclerView.Adapter ni la guía de desarrollo oficial relevante mencionan explícitamente el requisito del hilo principal en este momento (el momento es 2017) y ninguna de las reglas de inspección de pelusas de Android Studio parece preocuparse por este problema.
Pero, aquí hay una explicación de esto por el propio autor.
Tuve el mismo problema Acabo de resolverlo con la declaración del adapter
público antes de onCreate
of class.
PostAdapter postAdapter;
después de esto
postAdapter = new PostAdapter(getActivity(), posts);
recList.setAdapter(postAdapter);
por fin he llamado:
@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();
}
Que esto te ayude.