Android RecyclerView: Listas verticales y horizontales

Última actualización: 20/12/2020

android

El widget RecyclerView, sustituto de los vetustos ListView y GridView, es probablemente el elemento gráfico más importante de Android y el primero que debe conocer un principiante pues es el componente estándar para mostrar listas de datos. Se trata de un ViewGroup cuya misión es mostrar vistas (filas o «rows») que se repiten múltiples veces, por ejemplo un listado, pero con datos distintos. Estas vistas se van reutilizando para generar solamente aquellas que se muestran en pantalla con el objetivo de minimizar el consumo de memoria.

RecyclerView por sí mismo no proporciona mucho más que este reciclado y un listener para seguir el scroll. De hecho, ni siquiera posiciona los elementos en pantalla o gestiona eventos que no sean el scroll y delega en otras clases la realización de algunas funcionalidades (por ejemplo las animaciones). Esta simplicidad y modularidad, lo cual posibilita la configuración y personalización extrema de RecyclerView, lo convierten en un poderoso componente porque permite construir cualquier interfaz gráfica en la que se requiera mostrar un listado de datos sin tener que limitarnos a lo ofrecido por ListView o GridView.

Con RecyclerView podemos tener listas horizontales (se verá al final de este tutorial), animar fácilmente los elementos de la vista, trabajar con múltiples layouts y tipos de divisores, integración con CoordinatorLayout…Es más, recientemente Google ha creado una nueva implementación del widget ViewPager, denominada ViewPager2, que utiliza de forma interna un RecylerView para ofrecer funcionalidades no disponibles en ViewPager. Este componente se estudia en el tutorial Diseño Android: Toolbar, pestañas y ViewPager2 con AndroidX y Material Components.

Nota: este tutorial se complementa con Diseño Android : Endless RecyclerView asíncrono.

Mostrar un listado

Empecemos a utilizar RecyclerView creando un listado típico con scroll vertical con filas de un sólo tipo. En concreto, una lista de colores.

package com.danielme.android.recyclerview.list;

/**
 * @author danielme.com
 */
public class Color {

  private final String name;
  private final String hex;

  public Color(String name, String hex) {
    this.hex = hex;
    this.name = name;
  }

  public String getName() {
    return name;
  }


  public String getHex() {
    return hex;
  }

}

Originalmente las clases necesarias fueron publicadas en un módulo propio dentro de la biblioteca de compatibilidad, pero su desarrollo se abandonó en favor de las librerías de AndroidX que a su vez forman parte de Android Jetpack.

Vayamos paso a paso.

  1. Importar el módulo de AndroidX que proporciona RecyclerView y sus clases asociadas. En Android Studio/Gradle (el proyecto de ejemplo tiene como target Android 9 – API 28 y requiere como mínimo Android 4.4 Kit Kat):

    implementation 'androidx.recyclerview:recyclerview:1.1.0'

    Este es el fichero build.gradle completo del ejemplo; también usaremos Material Components for Android.

    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 30
    
        defaultConfig {
            applicationId "com.danielme.android.recyclerview"
            minSdkVersion 19
            targetSdkVersion 30
            versionCode 1
            versionName "1.0"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'com.google.android.material:material:1.2.1'
        implementation 'androidx.recyclerview:recyclerview:1.1.0'
    }
    

    Para más información sobre la importación de librerías en Eclipse ADT y Android Studio, consultar este artículo.

  2. Crear el «layout» que mostrará el RecyclerView para cada fila del listado de datos que vamos a visualizar en pantalla. En el ejemplo se mostrará una imagen (simulada mediante un círculo de color sólido) junto a un título y a un subtitulo.

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/selectableItemBackground"
        android:orientation="vertical"
        android:paddingBottom="@dimen/row_padding"
        android:paddingLeft="@dimen/general_padding"
        android:paddingRight="@dimen/general_padding"
        android:paddingTop="@dimen/row_padding">
    
        <View
            android:id="@+id/circleView"
            android:layout_width="@dimen/image"
            android:layout_height="@dimen/image"
            android:background="@drawable/circle"/>
    
        <TextView
            android:id="@+id/titleTextView"
            style="@style/RowTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_toEndOf="@+id/circleView"
            android:paddingLeft="@dimen/text_row_padding_image" />
    
        <TextView
            android:id="@+id/subtitleTextView"
            style="@style/RowSubtitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignStart="@+id/titleTextView"
            android:layout_below="@+id/titleTextView"
            android:paddingLeft="@dimen/text_row_padding_image" />
    
    </RelativeLayout>
    
  3. Implementar una clase que herede de RecyclerView.ViewHolder para gestionar el layout anterior. Más concretamente, aquí es donde accederemos a los elementos gráficos de la fila y les daremos valores.

    En el constructor recibimos un View que precisamente se corresponde con el layout de la fila y realizaremos el «mapeo» atributo-widget. Si la lista es muy sencilla, el ViewHolder suele crearse como una clase interna de la clase Adapter que veremos más adelante. Sin embargo, y como buena práctica, recomiendo abstraer el ViewHolder en su propia clase de tal modo que los widgets no sean accesibles desde el exterior de la clase y sólo puedan ser configurados por el propio ViewHolder, en nuestro ejemple invocando al método bindRow.

    package com.danielme.android.recyclerview.list;
    
    import android.graphics.drawable.GradientDrawable;
    import android.view.View;
    import android.widget.TextView;
    
    import androidx.annotation.NonNull;
    import androidx.recyclerview.widget.RecyclerView;
    
    class PaletteViewHolder extends RecyclerView.ViewHolder {
    
      private final View circleView;
      private final TextView titleTextView;
      private final TextView subtitleTextView;
    
      PaletteViewHolder(@NonNull View itemView) {
        super(itemView);
        circleView = itemView.findViewById(R.id.circleView);
        titleTextView = itemView.findViewById(R.id.titleTextView);
        subtitleTextView = itemView.findViewById(R.id.subtitleTextView);
      }
    
      void bindRow(@NonNull Color color) {
        titleTextView.setText(color.getName());
        subtitleTextView.setText(color.getHex());
        GradientDrawable gradientDrawable = (GradientDrawable) circleView.getBackground();
        int colorId = android.graphics.Color.parseColor(color.getHex());
        gradientDrawable.setColor(colorId);
      }
    
    }
    
  4. Crear la clase «adaptadora» heredando de RecyclerView.Adapter. Su responsabilidad es la creación y actualización de las vistas que representan cada fila de nuestra lista de colores, siendo estas vistas precisamente instancias del ViewHolder que acabamos que crear. Seguimos los siguientes pasos

    • Guardar la referencia a la estructura de datos con los datos a mostrar en el RecyclerView, en nuestro caso será la lista de la clase Color. Esta lista se recibirá en el constructor.
      public class MaterialPaletteAdapter extends RecyclerView.Adapter<MaterialPaletteAdapter.PaletteViewHolder> {
          private List<Color> data;
      
          public MaterialPaletteAdapter(@NonNull List<Color> data) {
              this.data = data;
          }
      ...
      
    • En el método VH onCreateViewHolder (ViewGroup parent, int viewType) se devuelve una instancia de nuestro ViewHolder para la View que recibimos. En esta View tendremos que «inflar» el layout de la fila.
          @Override
          @NonNull
          public PaletteViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
              View row = LayoutInflater.from(parent.getContext()).inflate(R.layout.row, parent, false);
              return new PaletteViewHolder(row);
          }
      

      El parámetro entero viewType es el retorno del método getItemViewType y permite distinguir qué vista debemos inflar para la fila, definida por su posición, que estamos procesando. En el ejemplo sólo tenemos un tipo de item en la lista, pero en el tutorial Diseño Android : Endless RecyclerView el recycler view además de las filas con los colores puede mostrar un footer lo que ha obligado a sobreescribir el método getItemViewType.

    • public void onBindViewHolder (VH holder, int position) establece en el ViewHolder que recibe como parámetro los datos correspondientes al elemento de la posición position.
        @Override
        public void onBindViewHolder(PaletteViewHolder holder, int position) {
          holder.bindRow(data.get(position));
        }
      
    • public abstract int getItemCount devuelve el número total de filas que tiene el Adapter, y que en nuestro caso es la lista de colores que hemos llamado «data».
          @Override
          public int getItemCount() {
              return data.size();
          }
      

    Cursos aplicaciones móviles

    El Adapter completo queda tal que así:

    package com.danielme.android.recyclerview.list;
    
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    
    import androidx.annotation.NonNull;
    import androidx.recyclerview.widget.RecyclerView;
    
    import java.util.List;
    
    /**
     * @author danielme.com
     */
    public class MaterialPaletteAdapter extends RecyclerView.Adapter<PaletteViewHolder> {
    
      private final List<Color> data;
    
      public MaterialPaletteAdapter(@NonNull List<Color> data) {
        this.data = data;
      }
    
      @Override
      @NonNull
      public PaletteViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View row = LayoutInflater.from(parent.getContext()).inflate(R.layout.row, parent, false);
        return new PaletteViewHolder(row);
      }
    
      @Override
      public void onBindViewHolder(PaletteViewHolder holder, int position) {
        holder.bindRow(data.get(position));
      }
    
      @Override
      public int getItemCount() {
        return data.size();
      }
    
    }
    

    Limpio y sencillo. Nuestro código se limita a crear las instancias de los ViewHolder y a poner en ellos los datos según vaya solicitando el Adapter a medida que vaya dibujando las filas en pantalla. De todo lo demás ya se encarga la librería.

  5. El RecyclerView lo vamos a mostrar dentro de la única pantalla de nuestro ejemplo. Lo añadimos y posicionamos dentro del XML de la pantalla tal y como haríamos con cualquier View.
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">
    
        <com.google.android.material.appbar.MaterialToolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:elevation="@dimen/elevation_toolbar"
            tools:targetApi="lollipop" />
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:scrollbars="vertical" />
    
    </LinearLayout>
    
  6. Por último, unimos todas las piezas y configuramos el RecyclerView en la Activity. Tenemos que construir y pasarle el Adapter.
     RecyclerView recyclerView = findViewById(R.id.recyclerView);
     recyclerView.setAdapter(new MaterialPaletteAdapter(colors));
    

    Comentaba en la introducción que RecylerView es muy sencillo y modular. Para que sepa cómo tiene que distribuir y dibujar las filas en la pantalla necesita de un RecyclerView.LayoutManager. AndroidX incluye tres implementaciones listas para ser utilizadas:

    • LinearLayoutManager. Simplemente apila los elementos uno detrás de otro disponiéndolos en vertical u horizontal.
    • GridLayoutManager para crear una «grid» o malla con celdas de igual tamaño y StaggeredGridLayoutManager para una grid con celdas de tamaño variable. El caso típico para utilizar estos layout es una galería de imágenes.

    Para el ejemplo, y en la práctica para la inmensa mayoría de listas que implementemos, vamos a utilizar un LinearLayoutManager con orientación vertical (es la orientación por defecto).

    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    
  7. Habitualmente tendremos un separador o divisor de cada fila, especialmente si no usamos tarjetas que ya proporcionan de serie el borde del contenido. Dibujar este divisor, offset, borde, etc es tarea de una implementación de RecyclerView.ItemDecoration. Hay una disponible en la clase DividerItemDecoration que aplica un divisor por defecto o bien un drawable que definamos (véase este ejemplo).
    DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
                    ((LinearLayoutManager) recyclerView.getLayoutManager()).getOrientation());
    recyclerView.addItemDecoration(dividerItemDecoration);
    

La Activity completa queda así

package com.danielme.android.recyclerview.list;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.appcompat.widget.Toolbar;

import java.util.ArrayList;
import java.util.List;

/**
 * @author danielme.com
 */
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        setupToolbar();

        setupRecyclerView();
    }

    private void setupToolbar() {
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
    }

    private void setupRecyclerView() {
        RecyclerView recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setAdapter(new MaterialPaletteAdapter(buildColors()));
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
                ((LinearLayoutManager) recyclerView.getLayoutManager()).getOrientation());
        recyclerView.addItemDecoration(dividerItemDecoration);
    }

    @SuppressWarnings("ResourceType")
    private List<Color> buildColors() {
        List<Color> colors = new ArrayList<>();
        colors.add(new Color(getString(R.string.blue), getResources().getString(R.color.blue)));
        colors.add(new Color(getString(R.string.indigo), getResources().getString(R.color.indigo)));
        colors.add(new Color(getString(R.string.red), getResources().getString(R.color.red)));
        colors.add(new Color(getString(R.string.green), getResources().getString(R.color.green)));
        colors.add(new Color(getString(R.string.orange), getResources().getString(R.color.orange)));
        colors.add(new Color(getString(R.string.grey), getResources().getString(R.color.bluegrey)));
        colors.add(new Color(getString(R.string.amber), getResources().getString(R.color.teal)));
        colors.add(new Color(getString(R.string.deeppurple), getResources().getString(R.color.deeppurple)));
        colors.add(new Color(getString(R.string.bluegrey), getResources().getString(R.color.bluegrey)));
        colors.add(new Color(getString(R.string.yellow), getResources().getString(R.color.yellow)));
        colors.add(new Color(getString(R.string.cyan), getResources().getString(R.color.cyan)));
        colors.add(new Color(getString(R.string.brown), getResources().getString(R.color.brown)));
        colors.add(new Color(getString(R.string.teal), getResources().getString(R.color.teal)));
        return colors;
    }

}

Antes de ejecutar nuestra app de ejemplo, echemos un vistazo a los estilos.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
        <item name="colorPrimary">@color/primary</item>
        <item name="colorPrimaryDark">@color/primaryDark</item>
        <item name="toolbarStyle">@style/Widget.MaterialComponents.Toolbar.PrimarySurface</item>
    </style>

    <style name="RowTitle" parent="TextAppearance.MaterialComponents.Headline6"/>

    <style name="RowSubtitle" parent="TextAppearance.MaterialComponents.Subtitle2">
        <item name="android:textColor">?android:attr/textColorSecondary</item>
    </style>

</resources>

El tema principal de la app es de tipo DayNight para tener soporte tanto para un tema claro como un tema oscuro. Con ese objetivo también se utilizan estilos propios de Material Components para la ActionBar\Toolbar y los textos de las filas. Para más información, recomiendo consultar los tutoriales

recyclerview tema claro y oscuro

El evento OnItemClickListener

A nuestra lista le falta una funcionalidad básica: el evento click en una fila. Tendremos que implementar la captura del evento ya que RecyclerView no la proporciona (recordemos nuevamente que sólo se encarga de deslizar y reciclar los items del adapter). Tenemos por tanto libertad para implementar este evento dónde y cómo queramos.

En el ViewHolder tenemos acceso al método getAdapterPosition que devuelve la posición del item asociado al ViewHolder en la colección del Adapter con todos los items (la lista de colores que hemos llamado «data»). Si capturamos en el ViewHolder la pulsación sobre el mismo sabremos cual es la posición seleccionada y podemos actuar en consecuencia; normalmente mostraremos otra pantalla con el detalle de los datos la fila pulsada.

Personalmente acostumbro a implementar el evento en la Activity y, puesto que el evento lo capturo en el ViewHolder, para poder atenderlo sigo los siguientes pasos:

  1. Nos aseguramos que el layout que muestra cada fila o item tiene como background un drawable con un selector de estados para aplicar un estilo distinto a la fila pulsada. En nuestro ejemplo ya estamos aplicando el que proporciona Android por defecto.
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/selectableItemBackground"
    
  2. Crear una interfaz con un método que recibe la View correspondiente a la fila pulsada y la posición del elemento en la lista. La implementación de este método es la acción a realizarse al pulsarse el elemento de la lista.
    package com.danielme.android.recyclerview.list;
    
    import android.view.View;
    
    public interface  RecyclerViewOnItemClickListener {
    
        void onClick(View v, int position);
    }
    
    
  3. El Adapter recibirá la implementación de la interfaz creada en el punto anterior y guardará la referencia para poder utilizarla dentro del ViewHolder.
    private final List<Color> data;
      private final RecyclerViewOnItemClickListener recyclerViewOnItemClickListener;
    
      public MaterialPaletteAdapter(@NonNull List<Color> data,
                                    @NonNull RecyclerViewOnItemClickListener
                                            recyclerViewOnItemClickListener) {
        this.data = data;
        this.recyclerViewOnItemClickListener = recyclerViewOnItemClickListener;
      }
    
  4. En el ViewHolder, se implementa la interfaz View.OnClickListener para capturar el evento de pulsación sobre la fila del RecyclerView que representa esa instancia del ViewHolder. Invocará al método de la interfaz RecyclerViewOnItemClickListener cuya implementación recibirá en el constructor desde el Adapter.
    package com.danielme.android.recyclerview.list;
    
    import android.graphics.drawable.GradientDrawable;
    import android.view.View;
    import android.widget.TextView;
    
    import androidx.annotation.NonNull;
    import androidx.recyclerview.widget.RecyclerView;
    
    class PaletteViewHolder extends RecyclerView.ViewHolder implements View
            .OnClickListener {
    
      private final View circleView;
      private final TextView titleTextView;
      private final TextView subtitleTextView;
      private final RecyclerViewOnItemClickListener listener;
    
      public PaletteViewHolder(@NonNull View itemView, @NonNull RecyclerViewOnItemClickListener listener) {
        super(itemView);
        circleView = itemView.findViewById(R.id.circleView);
        titleTextView = itemView.findViewById(R.id.titleTextView);
        subtitleTextView = itemView.findViewById(R.id.subtitleTextView);
        this.listener = listener;
        itemView.setOnClickListener(this);
      }
    
      public void bindRow(@NonNull Color color) {
        titleTextView.setText(color.getName());
        subtitleTextView.setText(color.getHex());
        GradientDrawable gradientDrawable = (GradientDrawable) circleView.getBackground();
        int colorId = android.graphics.Color.parseColor(color.getHex());
        gradientDrawable.setColor(colorId);
      }
    
      @Override
      public void onClick(View v) {
        listener.onClick(v, getAdapterPosition());
      }
    
    }
    
  5. En la Activity finalmente se implementa la respuesta al evento y se envía al Adapter. En el ejemplo se mostrará un Toast con la posición pulsada y el color correspondiente.
      recyclerView.setAdapter(new MaterialPaletteAdapter(colors, new RecyclerViewOnItemClickListener() {
          @Override
          public void onClick(View v, int position) {
            String text = position + " " + colors.get(position).getName();
            Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show();
          }
        }));
    

Así queda finalmente el proyecto.

android recyclerview custom adapter uml

Listas horizontales

Una limitación de ListView es la imposibilidad de crear una lista horizontal lo que nos obliga a implementarla nosotros mismos o bien recurrir a alguna librería externa. Pero con RecyclerView se puede elegir la orientación de los datos en el scroll simplemente configurando el LinearLayoutManager.

 recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));

Adicionalmente para ajustar la visualización del ejemplo haremos dos cambios.

  • La barra de scroll debe mostrarse en horizontal
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:scrollbars="horizontal" />
    
  • Para que cada fila no ocupe todo el ancho de la pantalla se configura su width como wrap_content
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="?attr/selectableItemBackground"
        android:orientation="vertical"
        android:paddingBottom="@dimen/row_padding"
        android:paddingLeft="@dimen/general_padding"
        android:paddingRight="@dimen/general_padding"
        android:paddingTop="@dimen/row_padding">
    

Con estos cambios tan simples ya tenemos el listado de colores en horizontal.

Código de ejemplo

El proyecto de ejemplo para Android Studio se encuentra disponible en GitHub. Para más información sobre cómo utilizar GitHub, consultar este artículo.

9 comentarios sobre “Android RecyclerView: Listas verticales y horizontales

  1. muchísimas gracias, me sirvió mucho pero tengo una duda y me gustaría saber si por favor me puedes ayudar..
    ¿Es posible crear un recycler principal (lista categorías) y que ese mismo recycler pueda alterar el contenido de un segundo recycler (lista de categoría seleccionada)?
    Gracias de antemano!

    1. Hola Moises, estoy empezando con esto del RecyclerView y no se si se podrá hacer lo que quieres con este tipo de view, pero si que te puedo decir que existen los ExpandablesListView que son listas en las que cada ítem de la lista contiene más ítems. Por desgracia, no he encontrado ningún articulo dedicado al ExpandableList escrito por Daniel.

      Te dejo los enlaces de donde saque esta maravilla:

      Parte I: https://www.youtube.com/watch?v=BkazaAeeW1Q
      Parte II: https://www.youtube.com/watch?v=_h94Kqyc-Ag

      Tutorial: http://www.easyway2in.blogspot.com.es/search?updated-max=2014-10-19T04:36:00-07:00&max-results=1&start=4&by-date=false

  2. Hola Muchas gracias por tu aporte, es un excelente tutorial, me podrias ayudar, al cambiarle la orientación de la lista, el texto que tengo queda en una sola línea a lo largo y no como párrafo, me podrías ayudar con este tema. Quedo agradecido.

    1. EL TextView muestra automáticamente todo el contenido de la cadena en más de una línea si hace falta siempre y cuando no se indique el atributo android:singleLine=»true» y tenga la suficiente altura para que quepan todas las líneas (por ejemplo con android:layout_height=»wrap_content»).

  3. Excelente articulo. Estoy empesando con el RecyclerView y ListView. Estuve agregando mas componente como un EditText para poner una cantidad, resulta que tengo 50 item y al agregar un numero en el primer item al hacer un scroll para dirigirme a otro articulo el valor que puse al primer item se pasa a otro. quiere saber como mitigar ese problema. Gracias

Deja un comentario

Este sitio utiliza Akismet para reducir el spam. Conoce cómo se procesan los datos de tus comentarios.