<?xml version="1.0" encoding="UTF-8" standalone="no"?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0"><channel><title>The Android Developer</title><description></description><managingEditor>noreply@blogger.com (Anonymous)</managingEditor><pubDate>Fri, 1 Nov 2024 04:32:45 -0700</pubDate><generator>Blogger http://www.blogger.com</generator><openSearch:totalResults xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/">2</openSearch:totalResults><openSearch:startIndex xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/">1</openSearch:startIndex><openSearch:itemsPerPage xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/">25</openSearch:itemsPerPage><link>http://the-android-developer.blogspot.com/</link><language>en-us</language><item><title>ViewHolder - Mejorando la implementación</title><link>http://the-android-developer.blogspot.com/2015/09/view-holder-mejorando-la-implementacion.html</link><category>AdapterView</category><category>Android</category><category>Design Patterns</category><author>noreply@blogger.com (Anonymous)</author><pubDate>Tue, 15 Sep 2015 17:05:00 -0700</pubDate><guid isPermaLink="false">tag:blogger.com,1999:blog-3258667592767034993.post-2165390431980672981</guid><description>&lt;body onload="prettyPrint()"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQO-64GzAgjZEyJQ_RKtuGhd7bIHZiBOJ11aWhtU_QWq7tFB4KnCODhs8cKYWtiSdDTfHzOQADZiD_-oUdkHuG4gGj4XigFlM697KN_eXiGvIs9Kyz16ihJo8JLubj4K1hhSrcxasJWeg/s1600/android.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQO-64GzAgjZEyJQ_RKtuGhd7bIHZiBOJ11aWhtU_QWq7tFB4KnCODhs8cKYWtiSdDTfHzOQADZiD_-oUdkHuG4gGj4XigFlM697KN_eXiGvIs9Kyz16ihJo8JLubj4K1hhSrcxasJWeg/s400/android.png" /&gt;&lt;/a&gt;&lt;/div&gt;

Retomando el tema del ViewHolder, vuelvo a escribir con intenciones de explicar como mejorar la implementación de este patrón. Cuando trabajamos en proyectos de aplicaciones que son grandes, muchas veces se hace necesario el hecho de mostrar ciertos datos, y recaemos directamente en ListViews, además, muchas veces esos datos no son simples y requieren que realicemos nuestros propios Adapters para mostrarlos.&lt;br/&gt;&lt;br/&gt;  

Para explicar la mejora de la implementación decidí utilizar el proyecto que realice en mi &lt;a href="http://the-android-developer.blogspot.com.ar/2015/09/que-es-el-viewholder-pattern_4.html" style="color: #3f51b5" target="_blank"&gt;primer post&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;  

Me oriento a explicar como facilitar además de la mejora de implementación también los tiempos de desarrollo. En mis proyectos, cuando requiero realizar múltiples Adapters mi estrategia se basa en crear una clase Adapter propia que va a ser la base para mis futuros custom Adapters.&lt;br/&gt;&lt;br/&gt;

Llegando a quedar de la siguiente forma: 
&lt;pre class="prettyprint"&gt;
&lt;code class="language-java"&gt;/**
 * Base Adapter class for our projects. 
 * 
 * @author jonatan.salas
 */
public class BaseListAdapter extends BaseAdapter {
    protected Context mContext;
    protected List mItems;

    /**
     * Constructor with parameters. 
     *
     * @param context - the context where this adapter is going to be used. 
     * @param items - the items to be represented. 
     */
    public BaseListAdapter(@NonNull final Context context,
                           @NonNull final List items) {
        this.mContext = context;
        this.mItems = items;
    }

    @Override
    public int getCount() {
        return mItems.size();
    }

    @Override
    public Object getItem(int position) {
        return mItems.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        return null;
    }

    /**
     * Class that represents a ViewHolder.
     * 
     * @author jonatan.salas
     */
    public static abstract class ViewHolder {

        /**
         * Constructor with parameters.
         * 
         * @param view - the parent view. 
         */
        protected ViewHolder(@NonNull final View view) {
            findViews(view);
        }

        /**
         * Method that finds the widgets we need in our custom item. 
         * 
         * @param view - the parent view. 
         */
        public abstract void findViews(@NonNull final View view);

        /**
         * Method that binds our widgets with the data. 
         * 
         * @param item - Object that has the data to show.  
         */
        public abstract void bindViews(@NonNull final Object item);
    }
}

&lt;/code&gt;
&lt;/pre&gt;
Como pueden ver ya defino algunos métodos, y además, genero una clase abstracta que define un ViewHolder del cual todos mis Adapters van a extender.&lt;br/&gt; En el primer post, luego de Aplicar el patrón ViewHolder mi adapter quedaba de la siguiente forma:  &lt;br /&gt;


&lt;pre class="prettyprint"&gt;
&lt;code class="language-java"&gt;/**
 * Custom adapter class
 *
 * @author jonatan.salas
 */
public class MessageAdapter extends BaseAdapter {
    private Context mContext;
    private List mItems;

    /**
     * Constructor with parameters
     *
     * @param context - the context where the adapter is going to be used.
     * @param items - the list of items that the adapter must represent.
     */
    public MessageAdapter(@NonNull final Context context,
                          @NonNull final List items) {
        this.mContext = context;
        this.mItems = items;
    }

    public void setItems(List items) {
        this.mItems = items;
    }

    @Override
    public int getCount() {
        return mItems.size();
    }

    @Override
    public Object getItem(int position) {
        return mItems.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        final ViewHolder viewHolder;

        if(null != convertView) {
            viewHolder = (ViewHolder) convertView.getTag();
        } else {
            convertView = LayoutInflater
                    .from(mContext)
                    .inflate(R.layout.message_item, parent, false);

            viewHolder = new ViewHolder(convertView);
            convertView.setTag(viewHolder);
        }

        onBindViews(viewHolder, position);

        return convertView;
    }

    /**
     * Method that bind views with the data to be shown.
     *
     * @param viewHolder - the viewHolder used to set the data.
     * @param position - the position where of the data to get.
     */
    private void onBindViews(@NonNull final ViewHolder viewHolder, final int position) {
        final MessageContent content = (MessageContent) getItem(position);

        viewHolder.profile.setImageDrawable(content.getImage());
        viewHolder.name.setText(content.getName());
        viewHolder.message.setText(content.getMessage());
        viewHolder.hour.setText(content.getHour());
    }

    /**
     * ViewHolder class used for caching the views needed by our adapter.
     *
     * @author jonatan.salas
     */
    protected static class ViewHolder {
        private ImageView profile;
        private TextView  name;
        private TextView message;
        private TextView hour;

        /**
         * Constructor with parameters.
         *
         * @param view - The root view that contains all the views needed.
         */
        public ViewHolder(@NonNull final View view) {
            profile = (ImageView) view.findViewById(R.id.profile_picture);
            name = (TextView) view.findViewById(R.id.contact_name);
            message = (TextView) view.findViewById(R.id.message);
            hour = (TextView) view.findViewById(R.id.message_hour);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

Ahora mi Adapter para mostrar los mensajes queda de la siguiente forma: &lt;/br&gt;

&lt;pre class="prettyprint"&gt;
&lt;code class="language-java"&gt;/**
 * Custom adapter class
 *
 * @author jonatan.salas
 */
public class MessageAdapter extends BaseListAdapter {

    /**
     * Constructor with parameters
     *
     * @param context - the context where the adapter is going to be used.
     * @param items - the list of items that the adapter must represent.
     */
    public MessageAdapter(@NonNull final Context context,
                          @NonNull final List items) {
        super(context, items);
    }

    @Override
    public View getView(int position, View view, ViewGroup parent) {
        final ViewHolder viewHolder;

        if(null != view) {
            viewHolder = (ViewHolder) view.getTag();
        } else {
            view = LayoutInflater
                    .from(mContext)
                    .inflate(R.layout.message_item, parent, false);

            viewHolder = ViewHolder.newInstance(view);
        }

        viewHolder.bindViews(getItem(position));

        return view;
    }

    /**
     * Implementation of ViewHolder
     *
     * @author jonatan.salas
     */
    protected static class ViewHolder extends BaseListAdapter.ViewHolder {
        private ImageView profile;
        private TextView  name;
        private TextView message;
        private TextView hour;

        /**
         * Constructor with parameters.
         *
         * @param view - the parent view.
         */
        protected ViewHolder(@NonNull final View view) {
            super(view);
        }

        @Override
        public void findViews(@NonNull final View view) {
            profile = (ImageView) view.findViewById(R.id.profile_picture);
            name = (TextView) view.findViewById(R.id.contact_name);
            message = (TextView) view.findViewById(R.id.message);
            hour = (TextView) view.findViewById(R.id.message_hour);
            view.setTag(this);
        }

        @Override
        public void bindViews(@NonNull final Object item) {
            final MessageContent content = (MessageContent) item;

            this.profile.setImageDrawable(content.getImage());
            this.name.setText(content.getName());
            this.message.setText(content.getMessage());
            this.hour.setText(content.getHour());
        }

        /**
         * Method that get instances of our ViewHolder class.
         *
         * @param view - the parent view
         * @return a new instance of ViewHolder
         */
        public static ViewHolder newInstance(@NonNull final View view) {
            return new ViewHolder(view);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

Ahora la particularidad de este Adapter, es que deje recaer la responsabilidad del binding de las vistas sobre el ViewHolder. Además de haber reducido cierta porción de código que se volvía reiterativa.&lt;/br&gt;&lt;/br&gt;

&lt;h2&gt;Conclusión:&lt;/h2&gt;
&lt;/br&gt;
Cuando los proyectos que tenemos son grandes, es necesario generar una clase Base de la cual podamos extender. También está bueno tener una clase abstracta ViewHolder definida dado a que nos permite mantener el código prolijo en distintas implementaciones de un Adapter.

&lt;p style="color:black;"&gt;Si deseas aprender un poco más al respecto, he dejado el código del post en un repositorio en Github.&lt;/p&gt;
Puedes visualizarlo accediendo a este &lt;a style="color: #3f51b5" href="https://github.com/jonisaa/ViewHolder-Pattern-Better-Implemetation-The-Android-Developer" target="_blank"&gt;link&lt;/a&gt;.


&lt;script src="https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js?lang=java&amp;amp;skin=default"&gt;&lt;/script&gt;
</description><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" height="72" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQO-64GzAgjZEyJQ_RKtuGhd7bIHZiBOJ11aWhtU_QWq7tFB4KnCODhs8cKYWtiSdDTfHzOQADZiD_-oUdkHuG4gGj4XigFlM697KN_eXiGvIs9Kyz16ihJo8JLubj4K1hhSrcxasJWeg/s72-c/android.png" width="72"/><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><georss:featurename xmlns:georss="http://www.georss.org/georss">Avellaneda, Buenos Aires, Argentina</georss:featurename><georss:point xmlns:georss="http://www.georss.org/georss">-34.665970093073973 -58.361434936523438</georss:point><georss:box xmlns:georss="http://www.georss.org/georss">-34.718195093073973 -58.442115936523436 -34.613745093073973 -58.280753936523439</georss:box></item><item><title>¿Qué es el ViewHolder Pattern?</title><link>http://the-android-developer.blogspot.com/2015/09/que-es-el-viewholder-pattern_4.html</link><category>AdapterView</category><category>Android</category><category>Design Patterns</category><author>noreply@blogger.com (Anonymous)</author><pubDate>Fri, 4 Sep 2015 15:46:00 -0700</pubDate><guid isPermaLink="false">tag:blogger.com,1999:blog-3258667592767034993.post-7421368447687919543</guid><description>&lt;body onload="prettyPrint()"&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQO-64GzAgjZEyJQ_RKtuGhd7bIHZiBOJ11aWhtU_QWq7tFB4KnCODhs8cKYWtiSdDTfHzOQADZiD_-oUdkHuG4gGj4XigFlM697KN_eXiGvIs9Kyz16ihJo8JLubj4K1hhSrcxasJWeg/s1600/android.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQO-64GzAgjZEyJQ_RKtuGhd7bIHZiBOJ11aWhtU_QWq7tFB4KnCODhs8cKYWtiSdDTfHzOQADZiD_-oUdkHuG4gGj4XigFlM697KN_eXiGvIs9Kyz16ihJo8JLubj4K1hhSrcxasJWeg/s400/android.png" /&gt;&lt;/a&gt;&lt;/div&gt;

Supongamos una situación en la que deseamos realizar una aplicación de mensajería, y la primer vista es una lista con los últimos mensajes, que presenta algunos datos como imagen de perfil, nombre, mensaje y hora de recepción.&lt;br /&gt;


Tenemos una clase que modela esto de la siguiente forma: &lt;br /&gt;


&lt;pre class="prettyprint"&gt;
&lt;code class="language-java"&gt;/**
 * POJO class representing a Message.
 */
public class MessageContent {
    private Drawable image;
    private String name;
    private String message;
    private String hour;

    /** Default Constructor **/
    public MessageContent() {}

    /**  Attribute setters &amp;&amp; getters **/
}
&lt;/code&gt;&lt;/pre&gt;
Y en nuestro Adapter propio tenemos este método definido de la siguiente manera: &lt;br /&gt;


&lt;pre class="prettyprint"&gt;
&lt;code class="language-java"&gt;@Override
public View getView(int position, View view, ViewGroup parent) {
    final MessageContent content = (MessageContent) getItem(position);   

    if(null == view) {
        view = LayoutInflater
                .from(mContext)
                .inflate(R.layout.message_item, parent, false);
    }

    final ImageView profile = (ImageView) view.findViewById(R.id.profile_picture);
    final TextView name = (TextView) view.findViewById(R.id.contact_name); 
    final TextView message = (TextView) view.findViewById(R.id.message); 
    final TextView hour = (TextView) view.findViewById(R.id.message_hour); 

    profile.setImageDrawable(content.getImage());
    name.setText(content.getName());
    message.setText(content.getMessage());
    hour.setText(content.getHour());

    return view;
}
&lt;/code&gt;&lt;/pre&gt;
Aquí tenemos un problema grave que afecta el rendimiento de nuestro ListView. Y el hecho se da en las múltiples veces que el método &lt;strong&gt;findViewById&lt;/strong&gt; va a ser invocado.&lt;br/&gt;

&lt;em&gt;Este método de la clase View genera un árbol que se recorre de forma recursiva en búsqueda del id solicitado. Al finalizar la búsqueda nos devuelve una instancia de Object la cual casteamos al tipo de objeto que necesitamos.&lt;/em&gt;&lt;br/&gt;

En nuestro caso, nuestro item contiene 4 vistas, con lo cual el método &lt;strong&gt;findViewById&lt;/strong&gt; se va a llamar cuatro veces. Además de eso, este método se ejecuta dentro de &lt;strong&gt;getView&lt;/strong&gt;. &lt;strong&gt;GetView&lt;/strong&gt; tiene la particularidad de que se va a llamar tantas veces como items haya en la lista o si hay items que necesitan ser actualizados. Esto hace que si la lista que tenemos sea muy grande, afecte el rendimiento del scroll del ListView, alterando la experiencia de los usuarios de nuestra app. &lt;br/&gt;

Para evitar que surja este problema, se ideo el patrón &lt;strong&gt;ViewHolder&lt;/strong&gt;, que consiste básicamente en cachear las vistas que necesita nuestro item en una &lt;strong&gt;clase estática&lt;/strong&gt; dentro de nuestra clase &lt;strong&gt;Adapter&lt;/strong&gt;. &lt;br /&gt;


Retomando el ejemplo anterior, la aplicación de ViewHolder sería la siguiente: &lt;br /&gt;


&lt;pre class="prettyprint"&gt;&lt;code class="language-java"&gt;/**
* ViewHolder class used for caching the views needed by our adapter.
*/
protected static class ViewHolder {
    private ImageView profile;
    private TextView  name;
    private TextView message;
    private TextView hour;

    /**
    * Constructor with parameters.
    *
    * @param view - The root view that contains all the views needed.
    */
    public ViewHolder(@NonNull final View view) {
        profile = (ImageView) view.findViewById(R.id.profile_picture);
        name = (TextView) view.findViewById(R.id.contact_name);
        message = (TextView) view.findViewById(R.id.message);
        hour = (TextView) view.findViewById(R.id.message_hour);
    }
}
&lt;/code&gt;&lt;/pre&gt;
Bien! Ahora que tenemos el ViewHolder definido es hora de utilizarlo, modificando &lt;strong&gt;getView&lt;/strong&gt; para que nos quede de la siguiente forma:&lt;br /&gt;


&lt;pre class="prettyprint"&gt;&lt;code class="language-java"&gt;@Override
public View getView(int position, View view, ViewGroup parent) {
    final MessageContent content = (MessageContent) getItem(position);
    final ViewHolder viewHolder;

    if(null != view) {
        viewHolder = (ViewHolder) view.getTag();
    } else {
        view = LayoutInflater
                .from(mContext)
                .inflate(R.layout.message_item, parent, false);

        viewHolder = new ViewHolder(view);
        convertView.setTag(viewHolder);
    }

    viewHolder.profile.setImageDrawable(content.getImage());
    viewHolder.name.setText(content.getName());
    viewHolder.message.setText(content.getMessage());
    viewHolder.hour.setText(content.getHour());

    return convertView;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Conclusión:&lt;/h2&gt;

&lt;p style="color:black;"&gt;Como habrán visto el &lt;strong&gt;ViewHolder&lt;/strong&gt; es un patrón bastante sencillo de implementar.&lt;/p&gt; 
&lt;p style="color:black;"&gt;Es &lt;strong&gt;altamente recomendable&lt;/strong&gt; utilizarlo en casos donde el layout de nuestro ítem contenga muchas vistas. Esto nos ayudará a mejorar la performance y la experiencia de nuestros usuarios.&lt;/p&gt; &lt;br /&gt;

&lt;p style="color:black;"&gt;Si deseas aprender un poco más al respecto, he dejado el código del post en un repositorio en Github.&lt;/p&gt;
Puedes visualizarlo accediendo a este &lt;a style="color: #3f51b5" href="https://github.com/jonisaa/ViewHolder-Pattern-The-Android-Developer" target="_blank"&gt;link&lt;/a&gt;.


&lt;script src="https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js?lang=java&amp;amp;skin=default"&gt;&lt;/script&gt;

&lt;/body&gt;</description><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" height="72" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQO-64GzAgjZEyJQ_RKtuGhd7bIHZiBOJ11aWhtU_QWq7tFB4KnCODhs8cKYWtiSdDTfHzOQADZiD_-oUdkHuG4gGj4XigFlM697KN_eXiGvIs9Kyz16ihJo8JLubj4K1hhSrcxasJWeg/s72-c/android.png" width="72"/><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total><georss:featurename xmlns:georss="http://www.georss.org/georss">Avellaneda, Buenos Aires, Argentina</georss:featurename><georss:point xmlns:georss="http://www.georss.org/georss">-34.6610756 -58.366973900000005</georss:point><georss:box xmlns:georss="http://www.georss.org/georss">-34.6871971 -58.407314400000004 -34.634954099999995 -58.326633400000006</georss:box></item></channel></rss>