Uso de la vista de reciclaje con una base de datos

Actualmente no hay implementación por defecto de RecyclerView.Adapter disponible.

Puede ser con el lanzamiento oficial, Google lo agregará.

Dado que no hay soporte para CursorAdapter con la consulta de reciclaje actual, ¿cómo podemos utilizar una vista de reciclaje con una base de datos? Alguna sugerencia ?

Si está ejecutando una consulta con un CursorLoader y desea RecyclerView en lugar de ListView.

Puedes probar mi CursorRecyclerViewAdapter: CursorAdapter en RecyclerView

Mi solución era mantener un miembro CursorAdapter en mi implementación recyclerView.Adapter. Luego pasar toda la manipulación de crear la nueva vista y enlazarla con el adaptador de cursor, algo así:

 public class MyRecyclerAdapter extends Adapter<MyRecyclerAdapter.ViewHolder> { // Because RecyclerView.Adapter in its current form doesn't natively // support cursors, we wrap a CursorAdapter that will do all the job // for us. CursorAdapter mCursorAdapter; Context mContext; public MyRecyclerAdapter(Context context, Cursor c) { mContext = context; mCursorAdapter = new CursorAdapter(mContext, c, 0) { @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { // Inflate the view here } @Override public void bindView(View view, Context context, Cursor cursor) { // Binding operations } }; } public static class ViewHolder extends RecyclerView.ViewHolder { View v1; public ViewHolder(View itemView) { super(itemView); v1 = itemView.findViewById(R.id.v1); } } @Override public int getItemCount() { return mCursorAdapter.getCount(); } @Override public void onBindViewHolder(ViewHolder holder, int position) { // Passing the binding operation to cursor loader mCursorAdapter.getCursor().moveToPosition(position); //EDITED: added this line as suggested in the comments below, thanks :) mCursorAdapter.bindView(holder.itemView, mContext, mCursorAdapter.getCursor()); } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { // Passing the inflater job to the cursor-adapter View v = mCursorAdapter.newView(mContext, mCursorAdapter.getCursor(), parent); return new ViewHolder(v); } } 

Dado que su pregunta dice "Cómo usar RecyclerView con una base de datos" y no está siendo específico si desea SQLite o cualquier otra cosa con el RecyclerView, voy a darle una solución que es muy óptima. Usaré Realm como base de datos y te permitiré mostrar todos los datos dentro de tu RecyclerView . Tiene soporte asíncrono de consultas, así, sin necesidad de utilizar Loaders o AsyncTask .

¿Por qué reino? Realm.io rendimiento Android

Paso 1

Agregue la dependencia gradle para Realm, la dependencia para la última versión se encuentra aquí

Paso 2

Cree su clase de modelo, por ejemplo, digamos algo simple como Data que tiene 2 campos, una cadena que se mostrará dentro de la fila RecyclerView y una marca de tiempo que se utilizará como itemId para permitir que el RecyclerView anime los elementos. Tenga en cuenta que extender RealmObject a continuación por lo que su clase de datos se almacenarán como una tabla y todas sus propiedades se almacenarán como columnas de esa tabla de datos . He marcado el texto de datos como clave principal en mi caso ya que no quiero que se agregue una cadena más de una vez. Pero si prefiere tener duplicados, haga la marca de tiempo como @PrimaryKey. Puede tener una tabla sin una clave primaria pero causará problemas si intenta actualizar la fila después de crearla. Realm no admite una clave primaria compuesta en el momento de escribir esta respuesta.

 import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; public class Data extends RealmObject { @PrimaryKey private String data; //The time when this item was added to the database private long timestamp; public String getData() { return data; } public void setData(String data) { this.data = data; } public long getTimestamp() { return timestamp; } public void setTimestamp(long timestamp) { this.timestamp = timestamp; } } 

Paso 3

Cree su diseño para ver cómo debe aparecer una sola fila dentro de RecyclerView . El diseño de un elemento de una sola fila dentro de nuestro adaptador es el siguiente

 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/area" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:background="@android:color/white" android:padding="16dp" android:text="Data" android:visibility="visible" /> </FrameLayout> 

Tenga en cuenta que he guardado un FrameLayout como root aunque tengo un TextView dentro. Planeo agregar más artículos en esta disposición y por lo tanto hecha flexible para ahora 🙂

Para la gente curiosa hacia fuera allí, éste es cómo un solo artículo mira actualmente. Disposición de una sola fila de artículos dentro de RecyclerView

Etapa 4

Cree su implementación RecyclerView.Adapter. En este caso, el objeto de origen de datos es un objeto especial denominado RealmResults que es básicamente un LOOK ArrayList , es decir, cuando se agregan o eliminan elementos de su tabla, este objeto RealmResults se actualiza automáticamente.

 import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import io.realm.Realm; import io.realm.RealmResults; import slidenerd.vivz.realmrecycler.R; import slidenerd.vivz.realmrecycler.model.Data; public class DataAdapter extends RecyclerView.Adapter<DataAdapter.DataHolder> { private LayoutInflater mInflater; private Realm mRealm; private RealmResults<Data> mResults; public DataAdapter(Context context, Realm realm, RealmResults<Data> results) { mRealm = realm; mInflater = LayoutInflater.from(context); setResults(results); } public Data getItem(int position) { return mResults.get(position); } @Override public DataHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = mInflater.inflate(R.layout.row_data, parent, false); DataHolder dataHolder = new DataHolder(view); return dataHolder; } @Override public void onBindViewHolder(DataHolder holder, int position) { Data data = mResults.get(position); holder.setData(data.getData()); } public void setResults(RealmResults<Data> results) { mResults = results; notifyDataSetChanged(); } @Override public long getItemId(int position) { return mResults.get(position).getTimestamp(); } @Override public int getItemCount() { return mResults.size(); } public void add(String text) { //Create a new object that contains the data we want to add Data data = new Data(); data.setData(text); //Set the timestamp of creation of this object as the current time data.setTimestamp(System.currentTimeMillis()); //Start a transaction mRealm.beginTransaction(); //Copy or update the object if it already exists, update is possible only if your table has a primary key mRealm.copyToRealmOrUpdate(data); //Commit the transaction mRealm.commitTransaction(); //Tell the Adapter to update what it shows. notifyDataSetChanged(); } public void remove(int position) { //Start a transaction mRealm.beginTransaction(); //Remove the item from the desired position mResults.remove(position); //Commit the transaction mRealm.commitTransaction(); //Tell the Adapter to update what it shows notifyItemRemoved(position); } public static class DataHolder extends RecyclerView.ViewHolder { TextView area; public DataHolder(View itemView) { super(itemView); area = (TextView) itemView.findViewById(R.id.area); } public void setData(String text) { area.setText(text); } } } 

Tenga en cuenta que estoy llamando notifyItemRemoved con la posición en la que ocurrió la eliminación, pero NO llamar notifyItemInserted o notifyItemRangeChanged porque no hay forma directa de saber qué posición el elemento se insertó en la base de datos ya que las entradas de Realm no se almacenan de una manera ordenada . El objeto RealmResults se actualiza automáticamente cada vez que se agrega, modifica o elimina un elemento nuevo de la base de datos, de modo que llamamos a notifyDataSetChanged mientras agregamos e insertamos entradas masivas. En este momento, probablemente le preocupan las animaciones que no se activarán porque está llamando a notifyDataSetChanged en lugar de notifyXXX . Ése es exactamente porqué tengo el método de getItemId devolver la marca de tiempo para cada fila del objeto de los resultados. La animación se logra en 2 pasos con notifyDataSetChanged si llama a setHasStableIds (true) y, a continuación, reemplaza getItemId para proporcionar algo distinto de la posición.

Paso 5

Permite agregar el RecyclerView a nuestra Actividad o Fragmento. En mi caso, estoy usando una Actividad. El archivo de diseño que contiene el RecyclerView es bastante trivial y se vería algo así.

 <android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/recycler" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="@dimen/text_margin" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> 

He añadido una aplicación: layout_behavior desde mi RecyclerView va dentro de un CoordinatorLayout que no he publicado en esta respuesta de brevedad.

Paso 6

Construir el RecyclerView en código y suministrar los datos que necesita. Cree e inicialice un objeto Realm dentro de onCreate y ciérrelo dentro de onDestroy prácticamente como cerrar una instancia SQLiteOpenHelper. En el más simple de su onCreate dentro de la actividad se verá así. El método initUi es donde ocurre toda la magia. Abro una instancia de Realm dentro de onCreate.

 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mRealm = Realm.getInstance(this); initUi(); } private void initUi() { //Asynchronous query RealmResults<Data> mResults = mRealm.where(Data.class).findAllSortedAsync("data"); //Tell me when the results are loaded so that I can tell my Adapter to update what it shows mResults.addChangeListener(new RealmChangeListener() { @Override public void onChange() { mAdapter.notifyDataSetChanged(); Toast.makeText(ActivityMain.this, "onChange triggered", Toast.LENGTH_SHORT).show(); } }); mRecycler = (RecyclerView) findViewById(R.id.recycler); mRecycler.setLayoutManager(new LinearLayoutManager(this)); mAdapter = new DataAdapter(this, mRealm, mResults); //Set the Adapter to use timestamp as the item id for each row from our database mAdapter.setHasStableIds(true); mRecycler.setAdapter(mAdapter); } 

Observe que en el primer paso, solicito a Realm que me proporcione todos los objetos de la clase Datos ordenados por su nombre de variable denominado datos de forma asíncrona. Esto me da un objeto RealmResults con 0 elementos en el hilo principal que estoy configurando en el adaptador. Añadí un RealmChangeListener para ser notificado cuando los datos han terminado de cargar desde el hilo de fondo donde llamo notifyDataSetChanged con mi adaptador. También he llamado a setHasStableIds a true para permitir que la implementación de RecyclerView.Adapter haga un seguimiento de los elementos que se agregan, eliminan o modifican. La función onDestroy para mi actividad cierra la instancia Realm

 @Override protected void onDestroy() { super.onDestroy(); mRealm.close(); } 

Este método initUi se puede llamar dentro de Creación de su Actividad o onCreateView o onViewCreated de su Fragmento. Observe las siguientes cosas.

Paso 7

BAM! Hay datos de la base de datos dentro de su RecyclerView asincronamente cargado sin CursorLoader, CursorAdapter, SQLiteOpenHelper con animaciones. La imagen GIF que se muestra aquí es un poco laggy, pero las animaciones se producen cuando se agregan elementos o eliminarlos.

Datos de la base de datos dentro de RecyclerView

Usted mismo puede implementar todos los métodos necesarios. Recientemente hice mi propia implementación simplemente copiando el código de CursorAdapter.

 public class MyAdapter extends RecyclerView.Adapter<ViewHolder> { protected boolean mDataValid; protected boolean mAutoRequery; protected Cursor mCursor; protected Context mContext; protected int mRowIDColumn; protected ChangeObserver mChangeObserver; protected DataSetObserver mDataSetObserver; protected FilterQueryProvider mFilterQueryProvider; public static final int FLAG_AUTO_REQUERY = 0x01; public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02; public Cursor getCursor() { return mCursor; } //Recommended public MyAdapter(Context context, Cursor c, int flags) { init(context, c, flags); } public MyAdapter(Context context, Cursor c) { init(context, c, FLAG_AUTO_REQUERY); } public MyAdapter(Context context, Cursor c, boolean autoRequery) { init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER); } void init(Context context, Cursor c, int flags) { if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) { flags |= FLAG_REGISTER_CONTENT_OBSERVER; mAutoRequery = true; } else { mAutoRequery = false; } boolean cursorPresent = c != null; mCursor = c; mDataValid = cursorPresent; mContext = context; mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1; if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) { mChangeObserver = new ChangeObserver(); mDataSetObserver = new MyDataSetObserver(); } else { mChangeObserver = null; mDataSetObserver = null; } if (cursorPresent) { if (mChangeObserver != null) c.registerContentObserver(mChangeObserver); if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver); } } // Create new views (invoked by the layout manager) @Override public ViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) { // create a new view final View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.list_item, parent, false); // set the view's size, margins, paddings and layout parameters ViewHolder vh = new ViewHolder(view, mCursor, new ViewHolder.IMyViewHolderClicks() { @SuppressLint("NewApi") @Override public void onClick(Cursor cursor) { Log.e("Item :", cursor.getString(cursor.getColumnIndex(MyDatabaseHelper.MW_NAAM))); Intent intent = new Intent(TasksListFragment.this.getActivity(), DetailActivity.class); intent.putExtra(DetailActivity.EXTRA_PARAM_ID, cursor.getLong(cursor.getColumnIndex(MyDatabaseHelper.MW_ID))); ActivityOptions activityOptions = ActivityOptions.makeSceneTransitionAnimation( TasksListFragment.this.getActivity(), // Now we provide a list of Pair items which contain the view we can transitioning // from, and the name of the view it is transitioning to, in the launched activity new Pair<View, String>( view.findViewById(R.id.imageview_item), DetailActivity.VIEW_NAME_HEADER_IMAGE), new Pair<View, String>( view.findViewById(R.id.textview_name), DetailActivity.VIEW_NAME_HEADER_TITLE) ); // Now we can start the Activity, providing the activity options as a bundle startActivity(intent, activityOptions.toBundle()); // END_INCLUDE(start_activity) } }); return vh; } // Replace the contents of a view (invoked by the layout manager) @SuppressLint("NewApi") @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 final Cursor cursor = getItem(position); holder.mTextView.setText(cursor.getString(cursor.getColumnIndex(MyDatabaseHelper.MW_NAAM))); holder.mImageView.setTransitionName("grid:image:" + cursor.getLong(cursor.getColumnIndex(MyDatabaseHelper.MW_ID))); holder.mTextView.setTransitionName("grid:name:" + cursor.getLong(cursor.getColumnIndex(MyDatabaseHelper.MW_ID))); } //@Override // public View getView(int position, View view, ViewGroup viewGroup) { // return view; // } // Return the size of your dataset (invoked by the layout manager) @Override public int getItemCount() { return getCount(); } public int getCount() { if (mDataValid && mCursor != null) { return mCursor.getCount(); } else { return 0; } } public Cursor getItem(int position) { if (mDataValid && mCursor != null) { mCursor.moveToPosition(position); return mCursor; } else { return null; } } @Override public long getItemId(int position) { if (mDataValid && mCursor != null) { if (mCursor.moveToPosition(position)) { return mCursor.getLong(mRowIDColumn); } else { return 0; } } else { return 0; } } public Cursor swapCursor(Cursor newCursor) { if (newCursor == mCursor) { return null; } Cursor oldCursor = mCursor; if (oldCursor != null) { if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver); if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver); } mCursor = newCursor; if (newCursor != null) { if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver); if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver); mRowIDColumn = newCursor.getColumnIndexOrThrow("_id"); mDataValid = true; // notify the observers about the new cursor notifyDataSetChanged(); } else { mRowIDColumn = -1; mDataValid = false; // notify the observers about the lack of a data set notifyDataSetInvalidated(); } return oldCursor; } public void changeCursor(Cursor cursor) { Cursor old = swapCursor(cursor); if (old != null) { old.close(); } } public CharSequence convertToString(Cursor cursor) { return cursor == null ? "" : cursor.toString(); } public Cursor runQueryOnBackgroundThread(CharSequence constraint) { if (mFilterQueryProvider != null) { return mFilterQueryProvider.runQuery(constraint); } return mCursor; } public FilterQueryProvider getFilterQueryProvider() { return mFilterQueryProvider; } public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) { mFilterQueryProvider = filterQueryProvider; } protected void onContentChanged() { if (mAutoRequery && mCursor != null && !mCursor.isClosed()) { if (false) Log.v("Cursor", "Auto requerying " + mCursor + " due to update"); mDataValid = mCursor.requery(); } } private class ChangeObserver extends ContentObserver { public ChangeObserver() { super(new Handler()); } @Override public boolean deliverSelfNotifications() { return true; } @Override public void onChange(boolean selfChange) { onContentChanged(); } } private class MyDataSetObserver extends DataSetObserver { @Override public void onChanged() { mDataValid = true; notifyDataSetChanged(); } @Override public void onInvalidated() { mDataValid = false; notifyDataSetInvalidated(); } } private final DataSetObservable mDataSetObservable = new DataSetObservable(); public void registerDataSetObserver(DataSetObserver observer) { mDataSetObservable.registerObserver(observer); } public void unregisterDataSetObserver(DataSetObserver observer) { mDataSetObservable.unregisterObserver(observer); } public void notifyDataSetInvalidated() { mDataSetObservable.notifyInvalidated(); } } 

Hice un RecyclerViewCursorAdapter utilizando un SortedList como backend, extendiendo RecyclerView.Adapter

Se puede utilizar con SQLiteCursor y cargadores

Sólo otra respuesta, ya que no me gustó el aceptado (que imo no tiene un uso intuitivo).

La siguiente es mi propia implementación, que es muy similar a (y parcialmente inspirado por) SimpleCursorAdapter :

 public class RecyclerViewSimpleCursorAdapter extends RecyclerView.Adapter { private int mLayout; private Cursor mCursor; private String[] mFrom; private int[] mTo; private boolean mAutoRequery; private ContentObserver mContentObserver; /** * Standard constructor. * * @param layout resource identifier of a layout file that defines the views for this list item. The layout file should include at least those named views defined in "to" * @param c The database cursor. Can be null if the cursor is not available yet. * @param from A list of column names representing the data to bind to the UI. Can be null if the cursor is not available yet. * @param to The views that should display column in the "from" parameter. These should all be TextViews and ImageViews. The first N views in this list are given the values of the first N columns in the from parameter. Can be null if the cursor is not available yet. */ public RecyclerViewSimpleCursorAdapter(int layout, Cursor c, String[] from, int[] to, boolean autoRequery) { mLayout = layout; mCursor = c; mFrom = from; mTo = to; mAutoRequery = autoRequery; if (mAutoRequery) { initializeContentObserver(); } } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new RecyclerView.ViewHolder( LayoutInflater.from(parent.getContext()) .inflate(mLayout, parent, false) ) { }; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { mCursor.moveToPosition(position); if (mFrom == null || mTo == null) return; for (int i = 0; i < mFrom.length && i < mTo.length; i++) { String from = mFrom[i]; int columnIndex = mCursor.getColumnIndex(from); String value = mCursor.getString(columnIndex); View view = holder.itemView.findViewById(mTo[i]); if (view instanceof TextView) { ((TextView) view).setText(value); } else if (view instanceof ImageView) { try { ((ImageView) view).setImageResource(Integer.parseInt(value)); } catch (NumberFormatException nfe) { ((ImageView) view).setImageURI(Uri.parse(value)); } } else { throw new IllegalStateException(view.getClass().getName() + " is not a view that can be bound by this RecyclerViewSimpleCursorAdapter"); } } } @Override public int getItemCount() { return mCursor != null ? mCursor.getCount() : 0; } private void initializeContentObserver() { mContentObserver = new ContentObserver(new Handler()) { @Override public boolean deliverSelfNotifications() { return true; } @Override public void onChange(boolean selfChange) { notifyDataSetChanged(); } }; mCursor.registerContentObserver(mContentObserver); } /** * Change the underlying cursor to a new cursor. If there is an existing cursor it will be closed. * * @param cursor The new cursor to be used */ public void changeCursor(Cursor cursor) { Cursor oldCursor = mCursor; if (mAutoRequery) { if (mCursor != null) { mCursor.unregisterContentObserver(mContentObserver); } mContentObserver = new ContentObserver(new Handler()) { @Override public boolean deliverSelfNotifications() { return true; } @Override public void onChange(boolean selfChange) { notifyDataSetChanged(); } }; mCursor = cursor; if (mCursor != null) { mCursor.registerContentObserver(mContentObserver); } } notifyDataSetChanged(); if (oldCursor != null && oldCursor != mCursor) { oldCursor.close(); } } /** * Change the cursor and change the column-to-view mappings at the same time. * * @param cursor The database cursor. Can be null if the cursor is not available yet. * @param from A list of column names representing the data to bind to the UI. Can be null if the cursor is not available yet. * @param to The views that should display column in the "from" parameter. These should all be TextViews or ImageViews. The first N views in this list are given the values of the first N columns in the from parameter. Can be null if the cursor is not available yet. */ public void changeCursorAndColumns(Cursor cursor, String[] from, int[] to) { mFrom = from; mTo = to; changeCursor(cursor); } /** * Returns the cursor. * @return the cursor */ public Cursor getCursor() { return mCursor; } } 

Puede modificarlo para otros usos particulares, pero con un cursor funciona igual que SimpleCursorAdapter , sólo con un RecyclerView .

Después de recuperar datos de DB y almacenar en una lista, tal vez debería ser algo como esto:

  dbHelper = new BooksDbAdapter(this); dbHelper.open(); //Clean all data dbHelper.deleteAllZist(); //Add some data dbHelper.insertSomeRecords(); List<String> mylist = dbHelper.getArrayColumn(3); 

Agregar datos a la publicación de reciclaje

 list = new ArrayList<DataObject>(); Integer i=0; for (String lst : mylist) { list.add(i++, new DataObject(lst, "The RecyclerView widget is a more advanced and flexible version of ListView.")); } recyclerView = (RecyclerView) findViewById(R.id.recyclerview); recyclerView.setHasFixedSize(true); myRecAdapter = new RecyclerviewAdapter(list, Zist1ChapterActivity.this); 

A continuación se muestra mi implementación de cursoradapter para una recyclerview. Soporta OnItemClickListener, OnLongItemClickListener, OnfooterClickListener, secciones y pie de página. No soporta , Header, onHeaderClickListner, scroller rápido, encabezado pegajoso o secciones pegajosas.

Simplemente extienda este adaptador y cree su propio adaptador. Anule los métodos proporcionados. Y pasar el cursor de OnCursorLoadFinished method.if el adaptador ya está creado, a continuación, swapCursor ()

 package com.tracker.paisa; import android.database.Cursor; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.util.SparseBooleanArray; import android.view.ViewGroup; import java.util.ArrayList; import java.util.List; /** * @author Rahul Upadhyay (https://github.com/devDroidRaul) * Supports footer * Onitemclicklistener, OnItemLongClickListener, OnFooterClickListener * Supports Sections. * * Does Not support,Header, OnHeaderClickListener, FastScroller, StickySection (this can b done with item decor) * Pull requests are welcome for improvements. * * Override this to give logic to place subheaders between items. * public abstract boolean onPlaceSubheaderBetweenItems(int position); * * create seperate viewHolders for item, subheaders and footer. and return required views. * * @Override below methods for individual item type. * public abstract VH onCreateItemViewHolder(ViewGroup parent, int viewType); * public abstract SH onCreateSubheaderViewHolder(ViewGroup parent, int viewType); * public abstract FH onCreateFooterViewHolder(ViewGroup parent, int viewType); * * Bind your views with data here. * @Override below methods to bind data to individual item types. * public abstract void onBindSubHeaderViewHolder(SH holder, Cursor cursor); * public abstract void onBindItemViewHolder(VH holder, Cursor cursor); * public abstract void onBindFooterViewHolder(FH holder, int itemPosition); * * Item type -1,-2,-3 are reserved, kindly do not pass them in getItemViewType. */ public abstract class RecyclerViewCursorAdapter<SH extends RecyclerView.ViewHolder, VH extends RecyclerView.ViewHolder, FH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public static final String TAG = RecyclerViewCursorAdapter.class.getSimpleName(); private static final int TYPE_SECTION_HEADER = -1; private static final int TYPE_MAIN_HEADER = -2; private static final int TYPE_FOOTER = -3; // private int headerLayout=0,viewLayout=0; boolean createHeader; private List<Integer> subheaderPositions = new ArrayList<>(); private Cursor mCursor; private boolean mDataValid,footerRequired=false; private int mRowIDColumn; private SparseBooleanArray mSelectedItemsIds; // public RecyclerViewCursorAdapter() { } //constructor public RecyclerViewCursorAdapter(Cursor c,boolean footerRequired) { setHasStableIds(true); swapCursor(c); this.footerRequired = footerRequired; this.mSelectedItemsIds = new SparseBooleanArray(); } // interface for listning click on recycler view; public interface OnItemClickedListener{ void OnItemClick(int id, Object data); void onItemLongClick(int id); } OnItemClickedListener onItemClickedListener; public void setOnItemClickedListener(OnItemClickedListener onItemClickedListener) { this.onItemClickedListener = onItemClickedListener; } public interface OnFooterClickedListener{ void onFooterClick(Object data); } OnFooterClickedListener onFooterClickedListener; public void setOnFooterClickedListener( OnFooterClickedListener onFooterClickedListener){ this.onFooterClickedListener = onFooterClickedListener; } public interface OnHeaderClickedListener{ void onHeaderClick(Object data); } OnHeaderClickedListener onHeaderClickedListener; public void setOnHeaderClickedListener( OnHeaderClickedListener onHeaderClickedListener){ this.onHeaderClickedListener = onHeaderClickedListener; } private void initSubheaderPositions() { subheaderPositions.clear(); if(getItemSize() != 0) { //TODO:Handle This please. //subheaderPositions.add(0); } else { return; } for(int i = 1; i < getItemSize(); i++) { if(onPlaceSubheaderBetweenItems(i - 1)) { subheaderPositions.add(i + subheaderPositions.size()); } } } @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { initSubheaderPositions(); } /** * Called when adapter needs to know whether to place subheader between two neighboring * items. * * @return true if you want to place subheader between two neighboring * items. */ public abstract boolean onPlaceSubheaderBetweenItems(int position); public abstract VH onCreateItemViewHolder(ViewGroup parent, int viewType); public abstract SH onCreateSubheaderViewHolder(ViewGroup parent, int viewType); public abstract FH onCreateFooterViewHolder(ViewGroup parent, int viewType); public abstract void onBindSubHeaderViewHolder(SH holder, Cursor cursor); public abstract void onBindItemViewHolder(VH holder, Cursor cursor); public abstract void onBindFooterViewHolder(FH holder, int itemPosition); public abstract int getItemSize(); /** * Return the view type of the item at position for the purposes * of view recycling. * Don't return -1. It's reserved for subheader view type. */ public int getViewType(int position) { return 0; } @Override public final int getItemViewType(int position) { if(isSubheaderOnPosition(position)) { return TYPE_SECTION_HEADER; } if(footerRequired && getCursor().getPosition()==(getCursor().getCount()-1)){ return TYPE_FOOTER; }else { return getViewType(position); } } public boolean isFooterAdded(){ return footerRequired; } @Override public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { Log.d("RVCA-OCVH","create viewholder"); if(viewType == TYPE_SECTION_HEADER) { return onCreateSubheaderViewHolder(parent, viewType); } if(footerRequired&&viewType == TYPE_FOOTER){ return onCreateFooterViewHolder(parent, viewType); }else { return onCreateItemViewHolder(parent, viewType); } } @SuppressWarnings("unchecked") @Override public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { Log.d("RVCA-OBVH","bind viewholder"); Log.d("RVCA-OBVH","subheader position:"+isSubheaderOnPosition(position)); if(isSubheaderOnPosition(position)) { if (!mDataValid) { throw new IllegalStateException("Cannot bind viewholder when cursor is in invalid state."); } if (!mCursor.moveToPosition(getItemPositionForViewHolder(position))) { throw new IllegalStateException("Could not move cursor to position " + getItemPositionForViewHolder(position) + " when trying to bind viewholder"); } onBindSubHeaderViewHolder((SH)holder, mCursor); }if(footerRequired && position==getItemCount()-1){ Log.d("RVCA-OBVH","bind footerHolder"); onBindFooterViewHolder((FH) holder,position); } else { if (!mDataValid) { throw new IllegalStateException("Cannot bind viewholder when cursor is in invalid state."); } if (!mCursor.moveToPosition(getItemPositionForViewHolder(position))) { throw new IllegalStateException("Could not move cursor to position " + getItemPositionForViewHolder(position) + " when trying to bind viewholder"); } // if(!mCursor.isAfterLast()) { // mCursor.moveToPosition(position); onBindItemViewHolder((VH)holder, mCursor); } } @Override public final int getItemCount() { return getItemSize() + subheaderPositions.size()+(footerRequired?1:0); } public void notifyDataChanged() { initSubheaderPositions(); notifyDataSetChanged(); } public void notifyItemInsertedAtPosition(int itemPosition) { if (itemPosition == 0) { if (getItemCount() == 1 || onPlaceSubheaderBetweenItems(itemPosition)) { subheaderPositions.add(0, 0); increaseSubheaderPositions(1, 2); notifyItemRangeInserted(0, 2); } else { increaseSubheaderPositions(1, 1); notifyItemInserted(1); } } else if (itemPosition == getItemSize() - 1) { if (onPlaceSubheaderBetweenItems(itemPosition - 1)) { subheaderPositions.add(getItemCount() - 1); notifyItemRangeInserted(getItemCount() - 1, 2); } else { notifyItemInserted(getItemPositionInRecyclerView(itemPosition)); } } else { if (onPlaceSubheaderBetweenItems(itemPosition - 1) && onPlaceSubheaderBetweenItems(itemPosition)) { final int itemPositionInRv = getItemPositionInRecyclerView(itemPosition - 1); final int countOfSubheadersBeforePosition = getCountOfSubheadersBeforePosition(itemPositionInRv); subheaderPositions.add(countOfSubheadersBeforePosition, itemPositionInRv + 1); increaseSubheaderPositions(countOfSubheadersBeforePosition + 1, 2); notifyItemRangeInserted(itemPositionInRv + 1, 2); } else if (onPlaceSubheaderBetweenItems(itemPosition)){ final int itemPositionInRv = getItemPositionInRecyclerView(itemPosition - 1); increaseSubheaderPositions(getCountOfSubheadersBeforePosition(itemPositionInRv), 1); notifyItemInserted(itemPositionInRv + 1); } else if (onPlaceSubheaderBetweenItems(itemPosition - 1)) { final int itemPositionInRv = getItemPositionInRecyclerView(itemPosition); increaseSubheaderPositions(getCountOfSubheadersBeforePosition(itemPositionInRv), 1); notifyItemInserted(itemPositionInRv); } else { final int itemPositionInRv = getItemPositionInRecyclerView(itemPosition); increaseSubheaderPositions(getCountOfSubheadersBeforePosition(itemPositionInRv), 1); notifyItemInserted(itemPositionInRv); } } } public void notifyItemChangedAtPosition(int itemPosition) { final int itemPositionInRv = getItemPositionInRecyclerView(itemPosition); notifyItemChanged(itemPositionInRv); } public void notifyItemRemovedAtPosition(int itemPosition) { final int itemPositionInRv = getItemPositionInRecyclerView(itemPosition); for (int i = 1; i < subheaderPositions.size(); i++) { final int subheaderPosition = subheaderPositions.get(i); if (subheaderPosition > itemPositionInRv) { final int previousSubheaderPosition = subheaderPositions.get(i - 1); if (subheaderPosition - previousSubheaderPosition == 2) { subheaderPositions.remove(subheaderPositions.indexOf(previousSubheaderPosition)); decreaseSubheaderPositions(subheaderPositions.indexOf(subheaderPosition), 2); notifyItemRangeRemoved(itemPositionInRv - 1, 2); } else { decreaseSubheaderPositions(subheaderPositions.indexOf(subheaderPosition), 1); notifyItemRemoved(itemPositionInRv); } return; } } final int lastSubheaderPosition = subheaderPositions.get(subheaderPositions.size() - 1); if (itemPositionInRv - lastSubheaderPosition == 1 && getItemCount() == itemPosition + subheaderPositions.size()) { subheaderPositions.remove(subheaderPositions.size() - 1); notifyItemRangeRemoved(itemPositionInRv - 1, 2); } else { notifyItemRemoved(itemPositionInRv); } } public void setGridLayoutManager(final GridLayoutManager gridLayoutManager) { gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { if(subheaderPositions.contains(position)) { return gridLayoutManager.getSpanCount(); } else { return 1; } } }); } public boolean isSubheaderOnPosition(int position) { return subheaderPositions.contains(position); } public int getCountOfSubheadersBeforePosition(int position) { int count = 0; for(int subheaderPosition : subheaderPositions) { if(subheaderPosition < position) { count++; } } return count; } public int getItemPositionInRecyclerView(int position) { int countOfItems = 0; for (int i = 1; i < subheaderPositions.size(); i++) { final int previousSubheaderPosition = subheaderPositions.get(i - 1); final int nextSubheaderPosition = subheaderPositions.get(i); countOfItems += nextSubheaderPosition - previousSubheaderPosition - 1; if (countOfItems > position) { return position + i; } } return position + subheaderPositions.size(); } public int getItemPositionForViewHolder(int viewHolderPosition) { return viewHolderPosition - getCountOfSubheadersBeforePosition(viewHolderPosition); } private void decreaseSubheaderPositions(int startSubheaderPosition, int decreaseNum) { for (int i = startSubheaderPosition; i < subheaderPositions.size(); i++) { final int subheaderPosition = subheaderPositions.get(i); subheaderPositions.set(i, subheaderPosition - decreaseNum); } } private void increaseSubheaderPositions(int startSubheaderPosition, int increaseNum) { for (int i = startSubheaderPosition; i < subheaderPositions.size(); i++) { final int subheaderPosition = subheaderPositions.get(i); subheaderPositions.set(i, subheaderPosition + increaseNum); } } private List<Integer> getSubheaderPositions() { return subheaderPositions; } public int getSubheaderCount() { return subheaderPositions.size(); } public void swapCursor(Cursor newCursor) { Log.d("RVCA-SC","swap cursor"); if (newCursor == mCursor) { Log.d("RVCA-SC","same cursor doing nothing"); return; } if (newCursor != null) { Log.d("RVCA-SC","swap cursor not null"); mCursor = newCursor; mRowIDColumn = mCursor.getColumnIndexOrThrow("_id"); mDataValid = true; // notify the observers about the new cursor notifyDataChanged(); } else { Log.d("RVCA-SC","swap cursor null"); notifyItemRangeRemoved(0, getItemCount()); mCursor = null; mRowIDColumn = -1; mDataValid = false; } } public Cursor getCursor(){ return mCursor ; } @Override public long getItemId(int position) { if (isSubheaderOnPosition(position)) return position; else { int cursorPosition = getItemPositionForViewHolder(position); Cursor cursor = getCursor(); if (hasOpenCursor() && cursor.moveToPosition(cursorPosition)) { return cursor.getLong(cursor.getColumnIndex("_id")); } return NO_CURSOR_POSITION; } } public static final int NO_CURSOR_POSITION = -99; protected boolean hasOpenCursor() { Cursor cursor = getCursor(); if (cursor == null || cursor.isClosed()) { swapCursor(null); return false; } return true; } //Methods to do Selection public void toggleSelection(int position) { selectView(position, !mSelectedItemsIds.get(position)); } //Remove selected selections public void removeSelection() { mSelectedItemsIds = new SparseBooleanArray(); notifyDataSetChanged(); } //Put or delete selected position into SparseBooleanArray public void selectView(int position, boolean value) { if (value) mSelectedItemsIds.put(position, value); else mSelectedItemsIds.delete(position); // notifyItemChangedAtPosition(position); notifyDataSetChanged(); } //Get total selected count public int getSelectedCount() { return mSelectedItemsIds.size(); } //Return all selected ids public SparseBooleanArray getSelectedIds() { return mSelectedItemsIds; } public void setSelectedItemIds(SparseBooleanArray selectedItemIds){ this.mSelectedItemsIds=selectedItemIds; } } 

You can use SQLite database to store the details. It is easy to access the data. You can check my code on github. https://github.com/thiru-wta/ToDo

  database = new Database(this); getSupportActionBar().setTitle("To Do List"); etAddItems = (EditText) findViewById(R.id.etAddItem); btnAdd = (Button) findViewById(R.id.btnAdd); mRecyclerView = (RecyclerView) findViewById(R.id.listView); mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); adapter = new RecyclerAdapter(this, listData); mRecyclerView.setAdapter(adapter); btnAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { event_name = etAddItems.getText().toString(); String getname=""; database.storeEventDetails(event_name); getname = database.getEventDetails(event_name); if (getname.length() != 0) { listData.add(getname); etAddItems.setText(""); adapter.notifyData(listData); } 

Database methods:

  public void storeEventDetails(String event_name, long timerStart) { SQLiteDatabase db1 = getWritableDatabase(); db1.execSQL("insert into '"+event_details_tbl+"' values('" + event_name + "')"); db1.close(); } 

get method:

  public String getEventDetails(String event_name) { SQLiteDatabase db1 = getReadableDatabase(); Cursor cur = db1.rawQuery("select * from '"+event_details_tbl+"' where event_name ='" + event_name + "'", null); cur.moveToFirst(); String evName = null; if (cur != null) { do { int eventName = cur.getColumnIndex("event_name"); String ev_name = cur.getString(eventName); evName = ev_name; } while (cur.moveToNext()); } cur.close(); db1.close(); return evName; } 

You have simply to follow these steps:

  • In the recycler view.adapter , create an ArrayList or an Iterator initialized with a null value.
  • Create a method like the ex swapcursor, for example swapdata(Arraylist<T> data) . Inside you give a new values to the array list, iterator or any structure that system can iterate using the integer position int the bindview. The values of this method are passed in the onloaderfinished() . Then call in just after assignement notifydatachange() ; these will ask the RecyclerView to redraw all the list with new data of the ArrayList.

In the activity or fragment where you are using the loadercalback, create a method that transforms the cursor to an arraylist, or iterator, depending on the data structure chosen in the adapter.

This way you can always use the loadercalback with out blocking the main thread.

  • Utilizar SimpleCursorAdapter.ViewBinder para cambiar el color de TextView
  • Cómo ordenar los resultados de CursorLoader?
  • ¿Cómo actualizar ListView en ListFragment de FragmentActivity?
  • Cómo obtener datos en OnItemClickListener desde SimpleCursorAdapter
  • Android - Obtención de la lista de contactos con direcciones de calle, pero no con valores bajos como Skype, donde la dirección es sólo una ciudad y un estado
  • ¿Por qué cursorLoader no notificó cambios en los datos de origen?
  • ¿Cómo leer un DB de SQLite en android con un cursorloader?
  • Android: desplazamiento sin fin - ListView y Cursor
  • Creación de un adaptador de cursor simple personalizado
  • ¿Usando SimpleCursorAdapter con Spinner?
  • Android - ¿SimpleCursorAdapter permite múltiples diseños como BaseAdapter?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.