Como 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
, le daré una solución que es muy óptima. Usaré Realm como base de datos y te dejaré mostrar todos los datos dentro de tu RecyclerView
. También tiene soporte para consultas asíncronas sin usar Loaders
o AsyncTask
.
¿Por qué reino?
Paso 1
Agregue la dependencia de 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 RecyclerView
fila y una marca de tiempo que se utilizará como itemId para permitir RecyclerView
animar los elementos. Tenga en cuenta que extiendo a RealmObject
continuación debido a que su Data
clase se almacenará como una tabla y todas sus propiedades se almacenarán como columnas de esa tabla Data
. 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 principal, pero causará problemas si intenta actualizar la fila después de crearla. Realm no admite una clave principal compuesta al 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 de cómo debe aparecer una sola fila dentro del RecyclerView
. El diseño para un elemento de una sola fila dentro de nuestro Adapter
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 mantenido una FrameLayout
raíz como aunque tengo un TextView
interior. Planeo agregar más elementos en este diseño y, por lo tanto, lo hice flexible por ahora :)
Para las personas curiosas, así es como se ve un solo elemento actualmente.
Paso 4
Crea tu RecyclerView.Adapter
implementación. En este caso, el objeto de origen de datos es un objeto especial llamado RealmResults
que es básicamente un LIVE ArrayList
, en otras palabras, a medida que los elementos se agregan o eliminan de su tabla, este RealmResults
objeto 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 llamo notifyItemInserted
o notifyItemRangeChanged
porque no hay una forma directa de saber en qué posición se insertó el elemento en la base de datos, ya que las entradas del Reino no se almacenan de manera ordenada. El RealmResults
objeto se actualiza automáticamente cada vez que se agrega, modifica o elimina un nuevo elemento de la base de datos, por lo que llamamos notifyDataSetChanged
al agregar e insertar entradas masivas. En este punto, probablemente esté preocupado por las animaciones que no se activarán porque está llamando notifyDataSetChanged
en lugar de notifyXXX
métodos. Esa es exactamente la razón por la que el getItemId
método devuelve la marca de tiempo para cada fila del objeto de resultados. La animación se logra en 2 pasos con notifyDataSetChanged
si llama setHasStableIds(true)
y luego anulagetItemId
para proporcionar algo más que solo el puesto.
Paso 5
Vamos a agregar el RecyclerView
a nuestro Activity
o Fragment
. En mi caso, estoy usando un Activity
. El archivo de diseño que contiene RecyclerView
es bastante trivial y se vería 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 agregado una app:layout_behavior
desde mi entrada RecyclerView
dentro de la CoordinatorLayout
cual no he publicado en esta respuesta por brevedad.
Paso 6
Construya el RecyclerView
código de entrada y proporcione los datos que necesita. Cree e inicialice un objeto Realm dentro onCreate
y ciérrelo dentro de onDestroy
manera similar a cerrar una SQLiteOpenHelper
instancia. Como mínimo, su onCreate
interior Activity
se verá así. El initUi
método es donde sucede toda la magia. Abro una instancia de Realm dentro 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);
}
Tenga en cuenta que en el primer paso, le pido a Realm que me dé todos los objetos de la Data
clase ordenados por su nombre de variable llamado datos de manera asíncrona. Esto me da un RealmResults
objeto con 0 elementos en el hilo principal que estoy configurando en el Adapter
. Agregué una RealmChangeListener
para recibir una notificación cuando los datos hayan terminado de cargarse desde el hilo de fondo donde llamo notifyDataSetChanged
con mi Adapter
. También he llamado setHasStableIds
a true para permitir que la RecyclerView.Adapter
implementación realice un seguimiento de los elementos que se agregan, eliminan o modifican. El onDestroy
para mi Activity
cierra la instancia de Realm
@Override
protected void onDestroy() {
super.onDestroy();
mRealm.close();
}
Este método initUi
se puede llamar dentro onCreate
de su Activity
o de onCreateView
o onViewCreated
de su Fragment
. Observe las siguientes cosas.
Paso 7
BAM! Hay datos de la base de datos dentro de su RecyclerView
asynchonously cargado sin CursorLoader
, CursorAdapter
, SQLiteOpenHelper
con animaciones. La imagen GIF que se muestra aquí es un poco lenta, pero las animaciones ocurren cuando agrega elementos o los elimina.